Mercurial > hg > orthanc-stone
changeset 1591:5887a4f8594b
moving platform-specific files out of the "OrthancStone" folder
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + +##################################################################### +## Sanity check of the configuration +##################################################################### + +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +if (ORTHANC_SANDBOXED) + message(FATAL_ERROR "Cannot enable SDL in sandboxed environments") +endif() + +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() + + + +##################################################################### +## Configure SDL +##################################################################### + +message("SDL is enabled") +include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_SDL=1 + -DORTHANC_ENABLE_WASM=0 + ) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND + NOT MSVC) + # This is necessary when compiling EXE for Windows using MinGW + link_libraries(mingw32) +endif() + + + +##################################################################### +## Additional source files for SDL +##################################################################### + +list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/SdlWindow.cpp + ${SDL_SOURCES} + ) + +if (ENABLE_OPENGL) + list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/SdlOpenGLContext.cpp + ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp + ) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/OrthancStoneSdlParameters.cmake Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + +##################################################################### +## Load generic parameters +##################################################################### + +include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) + + +##################################################################### +## CMake parameters tunable by the user +##################################################################### + +# Advanced parameters to fine-tune linking against system libraries +set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2") + + +##################################################################### +## Internal CMake parameters to enable the optional subcomponents of +## the Stone of Orthanc +##################################################################### + +set(ENABLE_THREADS ON CACHE INTERNAL "")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlConfiguration.cmake Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,224 @@ +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +if (STATIC_BUILD OR NOT USE_SYSTEM_SDL) + 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) + + set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h") + if (NOT EXISTS "${TMP}") + file(WRITE "${TMP}" " +#include \"SDL_platform.h\" +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +") + endif() + + # General source files + file(GLOB SDL_SOURCES + ${SDL_SOURCES_DIR}/src/*.c + ${SDL_SOURCES_DIR}/src/atomic/*.c + ${SDL_SOURCES_DIR}/src/audio/*.c + ${SDL_SOURCES_DIR}/src/cpuinfo/*.c + ${SDL_SOURCES_DIR}/src/dynapi/*.c + ${SDL_SOURCES_DIR}/src/events/*.c + ${SDL_SOURCES_DIR}/src/file/*.c + ${SDL_SOURCES_DIR}/src/haptic/*.c + ${SDL_SOURCES_DIR}/src/joystick/*.c + ${SDL_SOURCES_DIR}/src/libm/*.c + ${SDL_SOURCES_DIR}/src/power/*.c + ${SDL_SOURCES_DIR}/src/render/*.c + ${SDL_SOURCES_DIR}/src/stdlib/*.c + ${SDL_SOURCES_DIR}/src/thread/*.c + ${SDL_SOURCES_DIR}/src/timer/*.c + ${SDL_SOURCES_DIR}/src/video/*.c + + ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c + #${SDL_SOURCES_DIR}/src/timer/dummy/*.c + ${SDL_SOURCES_DIR}/src/audio/dummy/*.c + ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c + ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c + ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c + #${SDL_SOURCES_DIR}/src/main/dummy/*.c + ${SDL_SOURCES_DIR}/src/video/dummy/*.c + ) + + add_definitions( + -DUSING_PREMAKE_CONFIG_H=1 + + -DSDL_AUDIO_DISABLED=1 + -DSDL_AUDIO_DRIVER_DUMMY=1 + -DSDL_FILESYSTEM_DISABLED=1 + -DSDL_FILESYSTEM_DUMMY=1 + -DSDL_FILE_DISABLED=1 + -DSDL_HAPTIC_DISABLED=1 + -DSDL_JOYSTICK_DISABLED=1 + + #-DSDL_THREADS_DISABLED=1 + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/core/linux/*.c + ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/pthread/*.c + ${SDL_SOURCES_DIR}/src/timer/unix/*.c + ${SDL_SOURCES_DIR}/src/video/x11/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_LOADSO_DLOPEN=1 + -DSDL_THREAD_PTHREAD=1 + -DSDL_TIMER_UNIX=1 + -DSDL_POWER_DISABLED=1 + + -DSDL_VIDEO_DRIVER_X11=1 + + -DSDL_ASSEMBLY_ROUTINES=1 + -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 + -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1 + -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1 + ) + + link_libraries(X11 Xext) + + if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry") + # Raspberry Pi has no support for OpenGL + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_ES2=1 + -DSDL_VIDEO_RENDER_OGL=1 + -DSDL_VIDEO_RENDER_OGL_ES2=1 + -DSDL_VIDEO_OPENGL_GLX=1 + -DSDL_VIDEO_OPENGL_EGL=1 + ) + endif() + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/audio/directsound/*.c + ${SDL_SOURCES_DIR}/src/audio/disk/*.c + ${SDL_SOURCES_DIR}/src/audio/winmm/*.c + ${SDL_SOURCES_DIR}/src/joystick/windows/*.c + ${SDL_SOURCES_DIR}/src/haptic/windows/*.c + ${SDL_SOURCES_DIR}/src/power/windows/*.c + + ${SDL_SOURCES_DIR}/src/main/windows/*.c + ${SDL_SOURCES_DIR}/src/core/windows/*.c + ${SDL_SOURCES_DIR}/src/loadso/windows/*.c + ${SDL_SOURCES_DIR}/src/render/direct3d/*.c + ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/psp/*.c + ${SDL_SOURCES_DIR}/src/render/opengles/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c # Don't include more files from "thread/generic/*.c"! + ${SDL_SOURCES_DIR}/src/thread/windows/*.c + ${SDL_SOURCES_DIR}/src/timer/windows/*.c + ${SDL_SOURCES_DIR}/src/video/windows/*.c + ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + # NB: OpenGL ES headers are not available in MinGW-W64 + add_definitions( + -DSDL_LOADSO_WINDOWS=1 + -DSDL_THREAD_WINDOWS=1 + -DSDL_TIMER_WINDOWS=1 + -DSDL_POWER_WINDOWS=1 + + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_WGL=1 + -DSDL_VIDEO_RENDER_D3D=1 + -DSDL_VIDEO_RENDER_OGL=1 + -DSDL_VIDEO_DRIVER_WINDOWS=1 + ) + + if (MSVC) + add_definitions( + -D__FLTUSED__ + -DHAVE_LIBC=1 + ) + else() + add_definitions( + -DHAVE_GCC_ATOMICS=1 + -DSDL_ASSEMBLY_ROUTINES=1 + ) + endif() + + link_libraries(imm32 winmm version) + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/pthread/*.c + ${SDL_SOURCES_DIR}/src/timer/unix/*.c + ${SDL_SOURCES_DIR}/src/video/cocoa/*.m + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_LOADSO_DLOPEN=1 + -DSDL_THREAD_PTHREAD=1 + -DSDL_TIMER_UNIX=1 + -DSDL_POWER_DISABLED=1 + + -DSDL_VIDEO_DRIVER_COCOA=1 + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_CGL=1 + -DSDL_VIDEO_RENDER_OGL=1 + + -DSDL_ASSEMBLY_ROUTINES=1 + -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 + ) + + find_library(CARBON_LIBRARY Carbon) + find_library(COCOA_LIBRARY Cocoa) + find_library(IOKIT_LIBRARY IOKit) + find_library(QUARTZ_LIBRARY QuartzCore) + link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY}) + + endif() + +else() + pkg_search_module(SDL2 REQUIRED sdl2) + include_directories(${SDL2_INCLUDE_DIRS}) + link_libraries(${SDL2_LIBRARIES}) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlOpenGLContext.cpp Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SdlOpenGLContext.h" +#include "../../../OrthancStone/Sources/StoneException.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 <GL/glew.h> +#endif + +#include <OrthancException.h> + +namespace OrthancStone +{ + SdlOpenGLContext::SdlOpenGLContext(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) + : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling) + , context_(NULL) + { + 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) + { + LOG(ERROR) << glewGetErrorString(err); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot initialize glew"); + } + + isGlewInitialized_ = true; + } + } +#endif + } + + + SdlOpenGLContext::~SdlOpenGLContext() + { + SDL_GL_DeleteContext(context_); + } + + void SdlOpenGLContext::MakeCurrent() + { + if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0) + { + const char* errText = SDL_GetError(); + std::stringstream ss; + ss << "Cannot set current OpenGL context. SDL error text: " << errText; + std::string errStr = ss.str(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str()); + } + + // This makes our buffer swap synchronized with the monitor's vertical refresh + SDL_GL_SetSwapInterval(1); + } + + + void SdlOpenGLContext::SwapBuffer() + { + // Swap our buffer to display the current contents of buffer on screen + SDL_GL_SwapWindow(window_.GetObject()); + } + + + unsigned int SdlOpenGLContext::GetCanvasWidth() const + { + int w = 0; + SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL); + return static_cast<unsigned int>(w); + } + + + unsigned int SdlOpenGLContext::GetCanvasHeight() const + { + int h = 0; + SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h); + return static_cast<unsigned int>(h); + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlOpenGLContext.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" +#include "SdlWindow.h" + +#include <Compatibility.h> // For ORTHANC_OVERRIDE + +#include <SDL_render.h> + +#include <Enumerations.h> + +namespace OrthancStone +{ + class SdlOpenGLContext : public OpenGL::IOpenGLContext + { + private: + SdlWindow window_; + SDL_GLContext context_; + + public: + SdlOpenGLContext(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + ~SdlOpenGLContext(); + + SdlWindow& GetWindow() + { + return window_; + } + + virtual bool IsContextLost() ORTHANC_OVERRIDE + { + // On desktop applications, an OpenGL context should never be lost + return false; + } + + virtual void MakeCurrent() ORTHANC_OVERRIDE; + + virtual void SwapBuffer() ORTHANC_OVERRIDE; + + unsigned int GetCanvasWidth() const; + + unsigned int GetCanvasHeight() const; + + void ToggleMaximize() + { + window_.ToggleMaximize(); + } + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlViewport.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,236 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "SdlViewport.h" + +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" + +#include <OrthancException.h> + +#include <boost/make_shared.hpp> + +namespace OrthancStone +{ + ICompositor& SdlViewport::SdlLock::GetCompositor() + { + if (that_.compositor_.get() == NULL) + { + // The derived class should have called "AcquireCompositor()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return *that_.compositor_; + } + } + + + void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) + { + if (compositor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + compositor_.reset(compositor); + } + + + SdlViewport::SdlViewport() + { + refreshEvent_ = SDL_RegisterEvents(1); + + if (refreshEvent_ == static_cast<uint32_t>(-1)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void SdlViewport::PostConstructor() + { + controller_ = boost::make_shared<ViewportController>(shared_from_this()); + } + + void SdlViewport::SendRefreshEvent() + { + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = refreshEvent_; + SDL_PushEvent(&event); // This function is thread-safe, and can be called from other threads safely. + } + + + void SdlViewport::UpdateSize(unsigned int width, unsigned int height) + { + SdlLock lock(*this); + lock.GetCompositor().SetCanvasSize(width, height); + lock.Invalidate(); + } + + + SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) : + context_(title.c_str(), width, height, allowDpiScaling) + { + AcquireCompositor(new OpenGLCompositor(context_)); // (*) + } + + + void SdlOpenGLViewport::RefreshCanvasSize() + { + UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight()); + } + + + boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create( + const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) + { + boost::shared_ptr<SdlOpenGLViewport> that = + boost::shared_ptr<SdlOpenGLViewport>(new SdlOpenGLViewport(title, width, height, allowDpiScaling)); + that->SdlViewport::PostConstructor(); + return that; + } + + uint32_t SdlOpenGLViewport::GetSdlWindowId() + { + const SdlWindow& sdlWindowWrapper = context_.GetWindow(); + SDL_Window* sdlWindow = sdlWindowWrapper.GetObject(); + Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); + return sdlWindowId; + } + + SdlOpenGLViewport::~SdlOpenGLViewport() + { + // Make sure that the "OpenGLCompositor" is destroyed BEFORE the + // "OpenGLContext" it references (*) + ClearCompositor(); + } + + + void SdlOpenGLViewport::Paint() + { + SdlLock lock(*this); + lock.GetCompositor().Refresh(lock.GetController().GetScene()); + } + + + void SdlOpenGLViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + context_.ToggleMaximize(); + } + + + + void SdlCairoViewport::RefreshCanvasSize() + { + UpdateSize(window_.GetWidth(), window_.GetHeight()); + } + + SdlCairoViewport::SdlCairoViewport(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) : + window_(title, width, height, false /* enable OpenGL */, allowDpiScaling), + sdlSurface_(NULL) + { + AcquireCompositor(new CairoCompositor(width, height)); + } + + SdlCairoViewport::~SdlCairoViewport() + { + if (sdlSurface_) + { + SDL_FreeSurface(sdlSurface_); + } + } + + uint32_t SdlCairoViewport::GetSdlWindowId() + { + SDL_Window* sdlWindow = window_.GetObject(); + Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); + return sdlWindowId; + } + + void SdlCairoViewport::Paint() + { + SdlLock lock(*this); + + lock.GetCompositor().Refresh(lock.GetController().GetScene()); + CreateSdlSurfaceFromCompositor(dynamic_cast<CairoCompositor&>(lock.GetCompositor())); + + if (sdlSurface_ != NULL) + { + window_.Render(sdlSurface_); + } + } + + + void SdlCairoViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + window_.ToggleMaximize(); + } + + + // Assumes that the mutex is locked + void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor) + { + static const uint32_t rmask = 0x00ff0000; + static const uint32_t gmask = 0x0000ff00; + static const uint32_t bmask = 0x000000ff; + + const unsigned int width = compositor.GetCanvas().GetWidth(); + const unsigned int height = compositor.GetCanvas().GetHeight(); + + if (sdlSurface_ != NULL) + { + if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() && + sdlSurface_->w == static_cast<int>(width) && + sdlSurface_->h == static_cast<int>(height) && + sdlSurface_->pitch == static_cast<int>(compositor.GetCanvas().GetPitch())) + { + // The image from the compositor has not changed, no need to update the surface + return; + } + else + { + SDL_FreeSurface(sdlSurface_); + } + } + + sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32, + compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); + if (!sdlSurface_) + { + LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlViewport.h Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,203 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#if !defined(ORTHANC_ENABLE_SDL) +# error Macro ORTHANC_ENABLE_SDL must be defined +#endif + +#if ORTHANC_ENABLE_SDL != 1 +# error SDL must be enabled to use this file +#endif + +#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 + +#include "SdlOpenGLContext.h" +#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" +#include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" +#include "../../../OrthancStone/Sources/Viewport/IViewport.h" + +#include <SDL_events.h> + +// TODO: required for UndoStack injection +// I don't like it either :) +#include <boost/weak_ptr.hpp> + +#include <boost/thread/recursive_mutex.hpp> +#include <boost/enable_shared_from_this.hpp> + +namespace OrthancStone +{ + class UndoStack; + + class SdlViewport : public IViewport, + public boost::enable_shared_from_this<SdlViewport> + { + private: + boost::recursive_mutex mutex_; + uint32_t refreshEvent_; + boost::shared_ptr<ViewportController> controller_; + std::unique_ptr<ICompositor> compositor_; + + void SendRefreshEvent(); + + protected: + class SdlLock : public ILock + { + private: + SdlViewport& that_; + boost::recursive_mutex::scoped_lock lock_; + + public: + explicit SdlLock(SdlViewport& that) : + that_(that), + lock_(that.mutex_) + { + } + + virtual bool HasCompositor() const ORTHANC_OVERRIDE + { + return true; + } + + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.SendRefreshEvent(); + } + + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE + { + that_.RefreshCanvasSize(); + } + }; + + void ClearCompositor() + { + compositor_.reset(); + } + + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + + virtual void RefreshCanvasSize() = 0; + + protected: + SdlViewport(); + + void PostConstructor(); + + public: + bool IsRefreshEvent(const SDL_Event& event) const + { + return (event.type == refreshEvent_); + } + + virtual ILock* Lock() ORTHANC_OVERRIDE + { + return new SdlLock(*this); + } + + virtual uint32_t GetSdlWindowId() = 0; + + void UpdateSize(unsigned int width, + unsigned int height); + + virtual void ToggleMaximize() = 0; + + // Must be invoked from the main SDL thread + virtual void Paint() = 0; + }; + + + class SdlOpenGLViewport : public SdlViewport + { + private: + SdlOpenGLContext context_; + + SdlOpenGLViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + protected: + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string&, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + + virtual ~SdlOpenGLViewport(); + + virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; + + virtual void Paint() ORTHANC_OVERRIDE; + + virtual void ToggleMaximize() ORTHANC_OVERRIDE; + }; + + + class SdlCairoViewport : public SdlViewport + { + private: + SdlWindow window_; + SDL_Surface* sdlSurface_; + + void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor); + + SdlCairoViewport(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + protected: + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr<SdlCairoViewport> Create(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + + virtual ~SdlCairoViewport(); + + virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; + + virtual void Paint() ORTHANC_OVERRIDE; + + virtual void ToggleMaximize() ORTHANC_OVERRIDE; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlWindow.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,210 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SdlWindow.h" + +#if ORTHANC_ENABLE_SDL == 1 + +#include <Logging.h> +#include <OrthancException.h> + +#ifdef WIN32 +#include <windows.h> // for SetProcessDpiAware +#endif +// WIN32 + +#include <SDL_render.h> +#include <SDL_video.h> +#include <SDL.h> + +namespace OrthancStone +{ + SdlWindow::SdlWindow(const char* title, + unsigned int width, + unsigned int height, + bool enableOpenGl, + bool allowDpiScaling) : + maximized_(false) + { + // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release + // build mode, the application crashes whenever the SDL window is + // resized or maximized + + uint32_t windowFlags, rendererFlags; + if (enableOpenGl) + { + windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + rendererFlags = SDL_RENDERER_ACCELERATED; + } + else + { + 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 <windows.h> + //#include <ShellScalingAPI.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + //#include <comdef.h> 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, + SDL_WINDOWPOS_UNDEFINED, + width, height, windowFlags); + + if (window_ == NULL) + { + LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags); + if (!renderer_) + { + LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError(); + SDL_DestroyWindow(window_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + SdlWindow::~SdlWindow() + { + if (renderer_ != NULL) + { + SDL_DestroyRenderer(renderer_); + } + + if (window_ != NULL) + { + SDL_DestroyWindow(window_); + } + } + + + unsigned int SdlWindow::GetWidth() const + { + int w = -1; + SDL_GetWindowSize(window_, &w, NULL); + + if (w < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return static_cast<unsigned int>(w); + } + } + + + unsigned int SdlWindow::GetHeight() const + { + int h = -1; + SDL_GetWindowSize(window_, NULL, &h); + + if (h < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return static_cast<unsigned int>(h); + } + } + + + void SdlWindow::Render(SDL_Surface* surface) + { + /** + * "You are strongly encouraged to call SDL_RenderClear() to + * initialize the backbuffer before starting each new frame's + * drawing, even if you plan to overwrite every pixel." + * https://wiki.libsdl.org/SDL_RenderPresent + **/ + SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); + SDL_RenderClear(renderer_); // Clear the entire screen to our selected color + + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface); + if (texture != NULL) + { + SDL_RenderCopy(renderer_, texture, NULL, NULL); + SDL_DestroyTexture(texture); + } + + SDL_RenderPresent(renderer_); + } + + + void SdlWindow::ToggleMaximize() + { + if (maximized_) + { + SDL_RestoreWindow(window_); + maximized_ = false; + } + else + { + SDL_MaximizeWindow(window_); + maximized_ = true; + } + } + + + void SdlWindow::GlobalInitialize() + { + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + LOG(ERROR) << "Cannot initialize SDL"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void SdlWindow::GlobalFinalize() + { + SDL_Quit(); + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/Sdl/SdlWindow.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include <boost/noncopyable.hpp> + +// Forward declaration of SDL type to avoid clashes with DCMTK headers +// on "typedef Sint8", in "StoneInitialization.cpp" +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Surface; + +namespace OrthancStone +{ + class SdlWindow : public boost::noncopyable + { + private: + struct SDL_Window *window_; + struct SDL_Renderer *renderer_; + bool maximized_; + + public: + SdlWindow(const char* title, + unsigned int width, + unsigned int height, + bool enableOpenGl, + bool allowDpiScaling = true); + + ~SdlWindow(); + + SDL_Window *GetObject() const + { + return window_; + } + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + /** + * WARNING: "Refresh()" cannot only be called from the main SDL + * thread, in which the window was created. Otherwise, the + * renderer displays nothing! + **/ + void Render(struct SDL_Surface* surface); + + void ToggleMaximize(); + + static void GlobalInitialize(); + + static void GlobalFinalize(); + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,59 @@ +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + +##################################################################### +## Sanity check of the configuration +##################################################################### + +set(ENABLE_WEB_CLIENT OFF) +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +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_SDL=0 + -DORTHANC_ENABLE_WASM=1 + ) + + +##################################################################### +## Additional source files for WebAssembly +##################################################################### + +list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyCairoViewport.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyLoadersContext.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOracle.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyViewport.cpp + ) + +if (ENABLE_OPENGL) + list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOpenGLContext.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebGLViewport.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebGLViewportsRegistry.cpp + ) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + +##################################################################### +## Load generic parameters +##################################################################### + +include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) + + +##################################################################### +## CMake parameters tunable by the user +##################################################################### + +# None for now, but might contain stuff like memory settings + + +##################################################################### +## Internal CMake parameters to enable the optional subcomponents of +## the Stone of Orthanc +##################################################################### + +set(ENABLE_THREADS OFF CACHE INTERNAL "")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,211 @@ +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +cmake_minimum_required(VERSION 2.8.3) + +project(OrthancStoneModule) + + +# Warning message related to WebAssembly modules: We know that 1.38.41 +# DOES NOT work, but that 1.39.17 works. +message("") +message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===") +message("") + + +set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries") + + +# Ask for the generation of a side module +set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1") # Must be before "Compiler.cmake" + + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- + +set(USE_WASM ON CACHE BOOL "") + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + + +# Stone of Orthanc configuration +# --------------------------------------------------------------- + +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake) + +SET(ENABLE_DCMTK ON) +SET(ENABLE_DCMTK_NETWORKING OFF) +SET(ENABLE_DCMTK_TRANSCODING OFF) +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ORTHANC_SANDBOXED ON) + +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake) + + + + +################################################################################ + +# The source files that register a callback cannot be part of a side +# module, and must be compiled in the main module. The following +# command can be used to identify such files: +# $ grep -lrE 'emscripten_' ../../Sources/ + +set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS + ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp + ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp + ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp + ) + +list(REMOVE_ITEM ORTHANC_STONE_SOURCES + ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} + ) + +configure_file( + ${CMAKE_SOURCE_DIR}/../OrthancStone.h.in + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h + ) + +configure_file( + ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h + ) + +file( + COPY ${CMAKE_SOURCE_DIR}/../../Sources/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN OrthancStone.h EXCLUDE + PATTERN "Deprecated*" EXCLUDE + ) + +file( + COPY ${ORTHANC_FRAMEWORK_ROOT}/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN OrthancFramework.h EXCLUDE + ) + +add_executable(OrthancStoneModule + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ${CAIRO_SOURCES} + ${PIXMAN_SOURCES} + ${FREETYPE_SOURCES} + ) + +set_target_properties(OrthancStoneModule + PROPERTIES + COMPILE_FLAGS "${WASM_FLAGS}" + LINK_FLAGS "${WASM_LINKER_FLAGS}" + ) + +# CMake does not natively handle SIDE_MODULE, and believes that +# Emscripten produces a ".js" file (whereas it creates only the +# ".wasm"). Create a dummy ".js" for target to work. +add_custom_command( + TARGET OrthancStoneModule POST_BUILD + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js + ) + +file( + COPY ${BOOST_SOURCES_DIR}/boost/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + PATTERN "*.ipp" + ) + +file( + COPY ${JSONCPP_SOURCES_DIR}/include/json/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) + +file( + COPY ${CAIRO_SOURCES_DIR}/src/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) + +set(DCMTK_MODULES + dcmdata + config + ofstd + oflog + ) + +foreach (module IN LISTS DCMTK_MODULES) + file( + COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) +endforeach() + + +install( + TARGETS OrthancStoneModule + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib + ) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib + ) + +install( + DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/Include/boost + ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo + ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk + ${CMAKE_CURRENT_BINARY_DIR}/Include/json + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/ + ) + +install(FILES + ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/SharedLibrary/NOTES.txt Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,18 @@ + +Install Emscripten: +https://emscripten.org/docs/getting_started/downloads.html + +# cd ~/Downloads +# git clone https://github.com/emscripten-core/emsdk.git +# cd emsdk +# ./emsdk install 2.0.0 +# ./emsdk activate 2.0.0 + + +Then, if the installation path was "~/Downloads/emsdk/": + +# source ~/Downloads/emsdk/emsdk_env.sh +# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja +# ninja install + +=> The binaries will be put in "../../../../wasm-binaries/"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyCairoViewport.h" +# include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" +# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include <Viewport/WebAssemblyCairoViewport.h> +# include <Scene2D/CairoCompositor.h> +# include <Scene2DViewport/ViewportController.h> +#endif + + +#include <Images/Image.h> + +namespace OrthancStone +{ + void WebAssemblyCairoViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + compositor.Refresh(controller.GetScene()); + + // Create a temporary memory buffer for the canvas in JavaScript + Orthanc::ImageAccessor cairo; + dynamic_cast<CairoCompositor&>(compositor).GetCanvas().GetReadOnlyAccessor(cairo); + + const unsigned int width = cairo.GetWidth(); + const unsigned int height = cairo.GetHeight(); + + if (javascript_.get() == NULL || + javascript_->GetWidth() != width || + javascript_->GetHeight() != height) + { + javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, + true /* force minimal pitch */)); + } + + // Convert from BGRA32 memory layout (only color mode supported + // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 + // (as expected by HTML5 canvas). This simply amounts to + // swapping the B and R channels. Alpha channel is also set to + // full opacity (255). + uint8_t* q = reinterpret_cast<uint8_t*>(javascript_->GetBuffer()); + for (unsigned int y = 0; y < height; y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(cairo.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++) + { + q[0] = p[2]; // R + q[1] = p[1]; // G + q[2] = p[0]; // B + q[3] = 255; // A + + p += 4; + q += 4; + } + } + + // Execute JavaScript commands to blit the image buffer onto the + // 2D drawing context of the HTML5 canvas + EM_ASM({ + const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3); + const img = new ImageData(data, $2, $3); + const ctx = document.getElementById(UTF8ToString($0)).getContext('2d'); + ctx.putImageData(img, 0, 0); + }, + GetCanvasId().c_str(), // $0 + javascript_->GetBuffer(), // $1 + javascript_->GetWidth(), // $2 + javascript_->GetHeight()); // $3 + } + + + WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents) : + WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents) + { + RefreshCanvasSize(); + AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight())); + } + + + boost::shared_ptr<WebAssemblyCairoViewport> WebAssemblyCairoViewport::Create( + const std::string& canvasId, bool enableEmscriptenMouseEvents) + { + boost::shared_ptr<WebAssemblyCairoViewport> that = boost::shared_ptr<WebAssemblyCairoViewport>( + new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents)); + + that->WebAssemblyViewport::PostConstructor(); + return that; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "WebAssemblyViewport.h" + +namespace OrthancStone +{ + class WebAssemblyCairoViewport : public WebAssemblyViewport + { + private: + std::unique_ptr<Orthanc::ImageAccessor> javascript_; + + WebAssemblyCairoViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr<WebAssemblyCairoViewport> Create(const std::string& canvasId, + bool enableEmscriptenMouseEvents = true); + + virtual ~WebAssemblyCairoViewport() + { + ClearCompositor(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,97 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "WebAssemblyLoadersContext.h" + +namespace OrthancStone +{ + class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock + { + private: + WebAssemblyLoadersContext& that_; + + public: + explicit Locker(WebAssemblyLoadersContext& that) : + that_(that) + { + } + + virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE + { + return that_; + } + + virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE + { + return that_.oracle_.GetOracleObservable(); + } + + virtual void Schedule(boost::shared_ptr<IObserver> receiver, + int priority, + IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE + { + that_.scheduler_->Schedule(receiver, priority, command); + } + + virtual void CancelRequests(boost::shared_ptr<IObserver> receiver) ORTHANC_OVERRIDE + { + that_.scheduler_->CancelRequests(receiver); + } + + virtual void CancelAllRequests() ORTHANC_OVERRIDE + { + that_.scheduler_->CancelAllRequests(); + } + + virtual void AddLoader(boost::shared_ptr<IObserver> loader) ORTHANC_OVERRIDE + { + that_.loaders_.push_back(loader); + } + + virtual void GetStatistics(uint64_t& scheduledCommands, + uint64_t& processedCommands) ORTHANC_OVERRIDE + { + scheduledCommands = that_.scheduler_->GetTotalScheduled(); + processedCommands = that_.scheduler_->GetTotalProcessed(); + } + }; + + + WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) + { + oracle_.GetOracleObservable(); + scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_, + maxHighPriority, maxStandardPriority, maxLowPriority); + + if (!scheduler_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + ILoadersContext::ILock* WebAssemblyLoadersContext::Lock() + { + return new Locker(*this); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/Loaders/ILoadersContext.h" +#include "../../../OrthancStone/Sources/Loaders/OracleScheduler.h" +#include "WebAssemblyOracle.h" + +#include <list> + +namespace OrthancStone +{ + class WebAssemblyLoadersContext : public ILoadersContext + { + private: + class Locker; + + WebAssemblyOracle oracle_; + boost::shared_ptr<OracleScheduler> scheduler_; + std::list< boost::shared_ptr<IObserver> > loaders_; + + public: + WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + void SetLocalOrthanc(const std::string& root) + { + oracle_.SetLocalOrthanc(root); + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + oracle_.SetRemoteOrthanc(orthanc); + } + + void SetDicomCacheSize(size_t size) + { + oracle_.SetDicomCacheSize(size); + } + + virtual ILock* Lock() ORTHANC_OVERRIDE; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,199 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "WebAssemblyOpenGLContext.h" + +#include "../../../OrthancStone/Sources/StoneException.h" + +#include <OrthancException.h> + +#include <emscripten/html5.h> +#include <emscripten/em_asm.h> + +#include <boost/math/special_functions/round.hpp> + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext::PImpl + { + private: + std::string canvasSelector_; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; + bool isContextLost_; + + public: + explicit PImpl(const std::string& canvasSelector) : + canvasSelector_(canvasSelector), + isContextLost_(false) + { + // Context configuration + EmscriptenWebGLContextAttributes attr; + emscripten_webgl_init_context_attributes(&attr); + + // The next line might be necessary to print using + // WebGL. Sometimes, if set to "false" (the default value), + // the canvas was rendered as a fully white or black + // area. UNCONFIRMED. + attr.preserveDrawingBuffer = true; + + context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr); + if (context_ == 0) + { + std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \""); + message += canvasSelector; + message += "\" Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building."; + LOG(ERROR) << message; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message); + } + } + + void* DebugGetInternalContext() const + { + return reinterpret_cast<void*>(context_); + } + + bool IsContextLost() + { + //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec; + bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0); + isContextLost_ = apiFlag; + return isContextLost_; + } + + void SetLostContext() + { + isContextLost_ = true; + } + + ~PImpl() + { + try + { + EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_); + if (result != EMSCRIPTEN_RESULT_SUCCESS) + { + LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result; + } + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What(); + } + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what(); + } + catch (...) + { + LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl"; + } + } + + const std::string& GetCanvasSelector() const + { + return canvasSelector_; + } + + void MakeCurrent() + { + if (IsContextLost()) + { + LOG(ERROR) << "MakeCurrent() called on lost context " << context_; + throw StoneException(ErrorCode_WebGLContextLost); + } + + if (emscripten_is_webgl_context_lost(context_)) + { + LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_; + SetLostContext(); + throw StoneException(ErrorCode_WebGLContextLost); + } + + 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". + **/ + } + }; + + + WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) : + pimpl_(new PImpl(canvasSelector)) + { + } + + bool WebAssemblyOpenGLContext::IsContextLost() + { + return pimpl_->IsContextLost(); + } + + void WebAssemblyOpenGLContext::SetLostContext() + { + pimpl_->SetLostContext(); + } + + void* WebAssemblyOpenGLContext::DebugGetInternalContext() const + { + return pimpl_->DebugGetInternalContext(); + } + + void WebAssemblyOpenGLContext::MakeCurrent() + { + assert(pimpl_.get() != NULL); + pimpl_->MakeCurrent(); + } + + void WebAssemblyOpenGLContext::SwapBuffer() + { + assert(pimpl_.get() != NULL); + pimpl_->SwapBuffer(); + } + + const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const + { + assert(pimpl_.get() != NULL); + return pimpl_->GetCanvasSelector(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only be used if targeting WebAssembly +#endif + +#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 + +#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" + +#include <Compatibility.h> // For ORTHANC_OVERRIDE + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext + { + private: + class PImpl; + boost::shared_ptr<PImpl> pimpl_; + + public: + explicit WebAssemblyOpenGLContext(const std::string& canvasSelector); + + virtual bool IsContextLost() ORTHANC_OVERRIDE; + + virtual void MakeCurrent() ORTHANC_OVERRIDE; + + virtual void SwapBuffer() ORTHANC_OVERRIDE; + + /** + Returns true if the underlying context has been successfully recreated + */ + //bool TryRecreate(); + + const std::string& GetCanvasSelector() const; + + + /** + * This is for manual context loss (debug purposes) + **/ + void* DebugGetInternalContext() const; + void SetLostContext(); + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,818 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyOracle_Includes.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include <Oracle/WebAssemblyOracle_Includes.h> +#endif + +#include <OrthancException.h> +#include <Toolbox.h> + +#include <emscripten.h> +#include <emscripten/html5.h> +#include <emscripten/fetch.h> + + +#if ORTHANC_ENABLE_DCMTK == 1 +static unsigned int BUCKET_SOP = 1; +#endif + + +namespace OrthancStone +{ + class WebAssemblyOracle::TimeoutContext + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr<IObserver> receiver_; + std::unique_ptr<SleepOracleCommand> command_; + + public: + TimeoutContext(WebAssemblyOracle& oracle, + boost::weak_ptr<IObserver> receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + command_.reset(dynamic_cast<SleepOracleCommand*>(command)); + } + } + + void EmitMessage() + { + assert(command_.get() != NULL); + + SleepOracleCommand::TimeoutMessage message(*command_); + oracle_.EmitMessage(receiver_, message); + } + + static void Callback(void *userData) + { + std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); + context->EmitMessage(); + } + }; + + + /** + This object is created on the heap for every http request. + It is deleted in the success (or error) callbacks. + + This object references the receiver of the request. Since this is a raw + reference, we need additional checks to make sure we send the response to + the same object, for the object can be deleted and a new one recreated at the + same address (it often happens in the [single-threaded] browser context). + */ + class WebAssemblyOracle::FetchContext : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr<IObserver> receiver_; + std::unique_ptr<IOracleCommand> command_; + std::string expectedContentType_; + + public: + FetchContext(WebAssemblyOracle& oracle, + boost::weak_ptr<IObserver> receiver, + IOracleCommand* command, + const std::string& expectedContentType) : + oracle_(oracle), + receiver_(receiver), + command_(command), + expectedContentType_(expectedContentType) + { + if (Orthanc::Logging::IsTraceLevelEnabled()) + { + // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled + LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | " + << "receiver address = " << std::hex << receiver.lock().get(); + } + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const std::string& GetExpectedContentType() const + { + return expectedContentType_; + } + + IMessageEmitter& GetEmitter() const + { + return oracle_; + } + + boost::weak_ptr<IObserver> GetReceiver() const + { + return receiver_; + } + + void EmitMessage(const IMessage& message) + { + if (Orthanc::Logging::IsTraceLevelEnabled()) + { + // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled + LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = " + << std::hex << receiver_.lock().get() << std::dec; + } + + oracle_.EmitMessage(receiver_, message); + } + + IOracleCommand& GetCommand() const + { + return *command_; + } + + template <typename T> + const T& GetTypedCommand() const + { + return dynamic_cast<T&>(*command_); + } + +#if ORTHANC_ENABLE_DCMTK == 1 + void StoreInCache(const std::string& sopInstanceUid, + std::unique_ptr<Orthanc::ParsedDicomFile>& dicom, + size_t fileSize) + { + if (oracle_.dicomCache_.get()) + { + // Store it into the cache for future use + oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid, + dicom.release(), fileSize, true); + } + } +#endif + + static void SuccessCallback(emscripten_fetch_t *fetch) + { + /** + * Firstly, make a local copy of the fetched information, and + * free data associated with the fetch. + **/ + + if (fetch->userData == NULL) + { + LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!"; + return; + } + + std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + std::string answer; + if (fetch->numBytes > 0) + { + answer.assign(fetch->data, fetch->numBytes); + } + + + /** + * Retrieving the headers of the HTTP answer. + **/ + HttpHeaders headers; + +#if (__EMSCRIPTEN_major__ < 1 || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37)) +# warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API + + /** + * HACK - If emscripten < 1.38.37, the fetch API does not + * contain a way to retrieve the HTTP headers of the answer. We + * make the assumption that the "Content-Type" header of the + * response is the same as the "Accept" header of the + * query. This is fixed thanks to the + * "emscripten_fetch_get_response_headers()" function that was + * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26. + * + * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h + * https://github.com/emscripten-core/emscripten/pull/8486 + **/ + if (fetch->userData != NULL) + { + if (!context->GetExpectedContentType().empty()) + { + headers["Content-Type"] = context->GetExpectedContentType(); + } + } +#else + { + size_t size = emscripten_fetch_get_response_headers_length(fetch); + + std::string plainHeaders(size + 1, '\0'); + emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1); + + std::vector<std::string> tokens; + Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n'); + + for (size_t i = 0; i < tokens.size(); i++) + { + size_t p = tokens[i].find(':'); + if (p != std::string::npos) + { + std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p)); + std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1)); + headers[key] = value; + } + } + } +#endif + + LOG(TRACE) << "About to call emscripten_fetch_close"; + emscripten_fetch_close(fetch); + LOG(TRACE) << "Successfully called emscripten_fetch_close"; + + /** + * Secondly, use the retrieved data. + * IMPORTANT NOTE: the receiver might be dead. This is prevented + * by the object responsible for zombie check, later on. + **/ + try + { + if (context.get() == NULL) + { + LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + switch (context->GetCommand().GetType()) + { + case IOracleCommand::Type_Http: + { + HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);"; + OrthancRestApiCommand::SuccessMessage message + (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: + { + context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer + (context->GetReceiver(), context->GetEmitter(), answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer + (context->GetReceiver(), context->GetEmitter(), answer); + break; + } + + case IOracleCommand::Type_ParseDicomFromWado: + { +#if ORTHANC_ENABLE_DCMTK == 1 + const ParseDicomFromWadoCommand& command = + context->GetTypedCommand<ParseDicomFromWadoCommand>(); + + size_t fileSize; + std::unique_ptr<Orthanc::ParsedDicomFile> dicom + (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers)); + + { + ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true); + context->EmitMessage(message); + } + + context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); +#endif + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): " + << context->GetCommand().GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What(); + + { + OracleCommandExceptionMessage message(context->GetCommand(), e); + context->EmitMessage(message); + } + } + } + + static void FailureCallback(emscripten_fetch_t *fetch) + { + std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + +#if 0 + { + const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText); + char message[kEmscriptenStatusTextSize + 1]; + memcpy(message, fetch->statusText, kEmscriptenStatusTextSize); + message[kEmscriptenStatusTextSize] = 0; + + LOG(ERROR) << "Fetching " << fetch->url + << " failed, HTTP failure status code: " << fetch->status + << " | statusText = " << message + << " | numBytes = " << fetch->numBytes + << " | totalBytes = " << fetch->totalBytes + << " | readyState = " << fetch->readyState; + } +#endif + + { + OracleCommandExceptionMessage message + (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol)); + context->EmitMessage(message); + } + + /** + * TODO - The following code leads to an infinite recursion, at + * least with Firefox running on incognito mode => WHY? + **/ + emscripten_fetch_close(fetch); // Also free data on failure. + } + }; + + + + class WebAssemblyOracle::FetchCommand : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr<IObserver> receiver_; + std::unique_ptr<IOracleCommand> command_; + Orthanc::HttpMethod method_; + std::string url_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + std::string expectedContentType_; + bool hasCredentials_; + std::string username_; + std::string password_; + + public: + FetchCommand(WebAssemblyOracle& oracle, + boost::weak_ptr<IObserver> receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver), + command_(command), + method_(Orthanc::HttpMethod_Get), + timeout_(0), + hasCredentials_(false) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + void SetBody(std::string& body /* will be swapped */) + { + body_.swap(body); + } + + void AddHttpHeaders(const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } + } + + void SetTimeout(unsigned int timeout) + { + timeout_ = timeout; + } + + void SetCredentials(const std::string& username, + const std::string& password) + { + hasCredentials_ = true; + username_ = username; + password_ = password; + } + + void Execute() + { + if (command_.get() == NULL) + { + // Cannot call Execute() twice + LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + const char* method; + + switch (method_) + { + case Orthanc::HttpMethod_Get: + method = "GET"; + break; + + case Orthanc::HttpMethod_Post: + method = "POST"; + break; + + case Orthanc::HttpMethod_Delete: + method = "DELETE"; + break; + + case Orthanc::HttpMethod_Put: + method = "PUT"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + strcpy(attr.requestMethod, method); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE; + attr.onsuccess = FetchContext::SuccessCallback; + attr.onerror = FetchContext::FailureCallback; + attr.timeoutMSecs = timeout_ * 1000; + + if (hasCredentials_) + { + attr.withCredentials = EM_TRUE; + attr.userName = username_.c_str(); + attr.password = password_.c_str(); + } + + std::vector<const char*> headers; + headers.reserve(2 * headers_.size() + 1); + + std::string expectedContentType; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + std::string key; + Orthanc::Toolbox::ToLowerCase(key, it->first); + + if (key == "accept") + { + expectedContentType = it->second; + } + + if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header + { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + } + + headers.push_back(NULL); // Termination of the array of HTTP headers + + attr.requestHeaders = &headers[0]; + + char* requestData = NULL; + if (!body_.empty()) + requestData = reinterpret_cast<char*>(malloc(body_.size())); + + try + { + if (!body_.empty()) + { + memcpy(requestData, &(body_[0]), body_.size()); + attr.requestDataSize = body_.size(); + attr.requestData = requestData; + } + attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); + + // Must be the last call to prevent memory leak on error + emscripten_fetch(&attr, url_.c_str()); + } + catch(...) + { + if(requestData != NULL) + free(requestData); + throw; + } + } + }; + + + void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command, + const std::string& uri) const + { + if (isLocalOrthanc_) + { + command.SetUrl(localOrthancRoot_ + uri); + } + else + { + command.SetUrl(remoteOrthanc_.GetUrl() + uri); + command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders()); + + if (!remoteOrthanc_.GetUsername().empty()) + { + command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword()); + } + } + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, + HttpCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + fetch.SetUrl(command->GetUrl()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Post || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, + OrthancRestApiCommand* command) + { + try + { + //LOG(TRACE) << "*********** WebAssemblyOracle::Execute."; + //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command; + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Post || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute."; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What(); + } + //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what(); +// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute"; +// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, + GetOrthancImageCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, + GetOrthancWebViewerJpegCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, + ParseDicomFromWadoCommand* command) + { + std::unique_ptr<ParseDicomFromWadoCommand> protection(command); + +#if ORTHANC_ENABLE_DCMTK == 1 + if (dicomCache_.get()) + { + ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid()); + if (reader.IsValid() && + reader.HasPixelData()) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(), + reader.GetFileSize(), reader.HasPixelData()); + EmitMessage(receiver, message); + return; + } + } +#endif + + switch (command->GetRestCommand().GetType()) + { + case IOracleCommand::Type_Http: + { + const HttpCommand& rest = + dynamic_cast<const HttpCommand&>(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + fetch.SetUrl(rest.GetUrl()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + const OrthancRestApiCommand& rest = + dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + SetOrthancUrl(fetch, rest.GetUri()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver, + IOracleCommand* command) + { + LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = " + << std::hex << receiver.get(); + + std::unique_ptr<IOracleCommand> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + switch (command->GetType()) + { + case IOracleCommand::Type_Http: + Execute(receiver, dynamic_cast<HttpCommand*>(protection.release())); + break; + + case IOracleCommand::Type_OrthancRestApi: + Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); + break; + + case IOracleCommand::Type_Sleep: + { + unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); + emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, + new TimeoutContext(*this, receiver, protection.release())); + break; + } + + case IOracleCommand::Type_ParseDicomFromWado: +#if ORTHANC_ENABLE_DCMTK == 1 + Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release())); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK must be enabled to parse DICOM files"); +#endif + break; + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): " + << command->GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + return true; + } + + + void WebAssemblyOracle::SetDicomCacheSize(size_t size) + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (size == 0) + { + dicomCache_.reset(); + } + else + { + dicomCache_.reset(new ParsedDicomCache(size)); + } +#else + LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled"; +#endif + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/OrthancStone.h" + +#if !defined(ORTHANC_ENABLE_WASM) +# error The macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only compiled for WebAssembly +#endif + +#include "../../../OrthancStone/Sources/Messages/IObservable.h" +#include "../../../OrthancStone/Sources/Messages/IMessageEmitter.h" +#include "../../../OrthancStone/Sources/Oracle/IOracle.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../../../OrthancStone/Sources/Toolbox/ParsedDicomCache.h" +#endif + +#include <Compatibility.h> // For ORTHANC_OVERRIDE +#include <WebServiceParameters.h> + +#include <Enumerations.h> + +namespace OrthancStone +{ + class GetOrthancImageCommand; + class GetOrthancWebViewerJpegCommand; + class HttpCommand; + class OrthancRestApiCommand; + class ParseDicomFromWadoCommand; + + class WebAssemblyOracle : + public IOracle, + public IMessageEmitter + { + private: + typedef std::map<std::string, std::string> HttpHeaders; + + class TimeoutContext; + class FetchContext; + class FetchCommand; + + void SetOrthancUrl(FetchCommand& command, + const std::string& uri) const; + + void Execute(boost::weak_ptr<IObserver> receiver, + HttpCommand* command); + + void Execute(boost::weak_ptr<IObserver> receiver, + OrthancRestApiCommand* command); + + void Execute(boost::weak_ptr<IObserver> receiver, + GetOrthancImageCommand* command); + + void Execute(boost::weak_ptr<IObserver> receiver, + GetOrthancWebViewerJpegCommand* command); + + void Execute(boost::weak_ptr<IObserver> receiver, + ParseDicomFromWadoCommand* command); + + IObservable oracleObservable_; + bool isLocalOrthanc_; + std::string localOrthancRoot_; + Orthanc::WebServiceParameters remoteOrthanc_; + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr<ParsedDicomCache> dicomCache_; +#endif + + public: + WebAssemblyOracle() : + isLocalOrthanc_(false) + { + } + + virtual void EmitMessage(boost::weak_ptr<IObserver> observer, + const IMessage& message) ORTHANC_OVERRIDE + { + oracleObservable_.EmitMessage(observer, message); + } + + virtual bool Schedule(boost::shared_ptr<IObserver> receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; + + IObservable& GetOracleObservable() + { + return oracleObservable_; + } + + void SetLocalOrthanc(const std::string& root) + { + isLocalOrthanc_ = true; + localOrthancRoot_ = root; + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + isLocalOrthanc_ = false; + remoteOrthanc_ = orthanc; + } + + void SetDicomCacheSize(size_t size); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyOracle_Includes.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +/** + * This file serves as an indirection to avoid large "#if + * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp" + **/ + +#include "WebAssemblyOracle.h" + +#include "../../../OrthancStone/Sources/Oracle/OracleCommandExceptionMessage.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../../../OrthancStone/Sources/Oracle/ParseDicomSuccessMessage.h" +#endif + +#include "../../../OrthancStone/Sources/Oracle/GetOrthancImageCommand.h" +#include "../../../OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../../OrthancStone/Sources/Oracle/HttpCommand.h" +#include "../../../OrthancStone/Sources/Oracle/OrthancRestApiCommand.h" +#include "../../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h" +#include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,399 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyViewport.h" +# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +# include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" +# include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include <Viewport/WebAssemblyViewport.h> +# include <Toolbox/GenericToolbox.h> +# include <Scene2DViewport/ViewportController.h> +# include <Viewport/DefaultViewportInteractor.h> +#endif + + +#include <OrthancException.h> + +#include <boost/make_shared.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/math/special_functions/round.hpp> + +namespace OrthancStone +{ + static void ConvertMouseEvent(PointerEvent& target, + const EmscriptenMouseEvent& source, + const ICompositor& compositor) + { + int x = static_cast<int>(source.targetX); + int y = static_cast<int>(source.targetY); + + switch (source.button) + { + case 0: + target.SetMouseButton(MouseButton_Left); + break; + + case 1: + target.SetMouseButton(MouseButton_Middle); + break; + + case 2: + target.SetMouseButton(MouseButton_Right); + break; + + default: + target.SetMouseButton(MouseButton_None); + break; + } + + target.AddPosition(compositor.GetPixelCenterCoordinates(x, y)); + target.SetAltModifier(source.altKey); + target.SetControlModifier(source.ctrlKey); + target.SetShiftModifier(source.shiftKey); + } + + + class WebAssemblyViewport::WasmLock : public ILock + { + private: + WebAssemblyViewport& that_; + + public: + WasmLock(WebAssemblyViewport& that) : + that_(that) + { + } + + virtual bool HasCompositor() const ORTHANC_OVERRIDE + { + return that_.compositor_.get() != NULL; + } + + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE + { + if (that_.compositor_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *that_.compositor_; + } + } + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + assert(that_.controller_); + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.Invalidate(); + } + + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE + { + that_.RefreshCanvasSize(); + } + }; + + + EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); + + if (that->compositor_.get() != NULL && + that->controller_ /* should always be true */) + { + that->Paint(*that->compositor_, *that->controller_); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); + + if (that->compositor_.get() != NULL) + { + that->RefreshCanvasSize(); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); + + LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector(); + + if (that->compositor_.get() != NULL && + that->interactor_.get() != NULL) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + + that->controller_->HandleMousePress(*that->interactor_, pointer, + that->compositor_->GetCanvasWidth(), + that->compositor_->GetCanvasHeight()); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); + + if (that->compositor_.get() != NULL && + that->controller_->HasActiveTracker()) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + if (that->controller_->HandleMouseMove(pointer)) + { + that->Invalidate(); + } + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); + + if (that->compositor_.get() != NULL) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + that->controller_->HandleMouseRelease(pointer); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + void WebAssemblyViewport::Invalidate() + { + emscripten_request_animation_frame(OnRequestAnimationFrame, reinterpret_cast<void*>(this)); + } + + void WebAssemblyViewport::FitForPrint() + { + if (compositor_.get() != NULL && + controller_ /* should always be true */) + { + RefreshCanvasSize(); + compositor_->FitContent(controller_->GetScene()); + OnRequestAnimationFrame(0, reinterpret_cast<void*>(this)); // Mandatory to work with Firefox + } + } + + void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) + { + if (compositor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + compositor_.reset(compositor); + } + } + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 +// everything OK..... we're using the new setting +#else +#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.") +#endif + + WebAssemblyViewport::WebAssemblyViewport( + const std::string& canvasId, bool enableEmscriptenMouseEvents) : + canvasId_(canvasId), +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 + canvasCssSelector_("#" + canvasId), +#else + canvasCssSelector_(canvasId), +#endif + interactor_(new DefaultViewportInteractor), + enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents), + canvasWidth_(0), + canvasHeight_(0) + { + } + + void WebAssemblyViewport::PostConstructor() + { + boost::shared_ptr<IViewport> viewport = shared_from_this(); + controller_.reset(new ViewportController(viewport)); + + LOG(INFO) << "Initializing Stone viewport on HTML canvas: " + << canvasId_; + + if (canvasId_.empty() || + canvasId_[0] == '#') + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The canvas identifier must not start with '#'"); + } + + // Disable right-click on the canvas (i.e. context menu) + EM_ASM({ + document.getElementById(UTF8ToString($0)).oncontextmenu = + function(event) + { + event.preventDefault(); + } + }, + canvasId_.c_str() // $0 + ); + + // It is not possible to monitor the resizing of individual + // canvas, so we track the full window of the browser + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast<void*>(this), + false, + OnResize); + + if (enableEmscriptenMouseEvents_) + { + + // if any of this function causes an error in the console, please + // make sure you are using the new (as of 1.39.x) version of + // emscripten element lookup rules( pass + // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker. + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + OnMouseDown); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + OnMouseMove); + + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + OnMouseUp); + } + } + + WebAssemblyViewport::~WebAssemblyViewport() + { + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast<void*>(this), + false, + NULL); + + if (enableEmscriptenMouseEvents_) + { + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + NULL); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + NULL); + + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast<void*>(this), + false, + NULL); + } + } + + IViewport::ILock* WebAssemblyViewport::Lock() + { + return new WasmLock(*this); + } + + void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor) + { + if (interactor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + interactor_.reset(interactor); + } + } + + + void WebAssemblyViewport::RefreshCanvasSize() + { + double w, h; + emscripten_get_element_css_size(GetCanvasCssSelector().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_ = static_cast<unsigned int>(boost::math::iround(w)); + canvasHeight_ = static_cast<unsigned int>(boost::math::iround(h)); + } + else + { + canvasWidth_ = 0; + canvasHeight_ = 0; + } + + emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_); + + if (compositor_.get() != NULL) + { + compositor_->SetCanvasSize(canvasWidth_, canvasHeight_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebAssemblyViewport.h Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,139 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/OrthancStone.h" + +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only be used if targeting WebAssembly +#endif + +#include "../../../OrthancStone/Sources/Viewport/IViewport.h" +#include "../../../OrthancStone/Sources/Viewport/IViewportInteractor.h" + +#include <Compatibility.h> + +#include <emscripten.h> +#include <emscripten/html5.h> + +#include <memory> +#include <string> +#include <boost/enable_shared_from_this.hpp> + +namespace OrthancStone +{ + class WebAssemblyViewport : public IViewport, + public boost::enable_shared_from_this<WebAssemblyViewport> + + { + private: + class WasmLock; + + std::string canvasId_; + std::string canvasCssSelector_; + std::unique_ptr<ICompositor> compositor_; + std::unique_ptr<ViewportController> controller_; + std::unique_ptr<IViewportInteractor> interactor_; + bool enableEmscriptenMouseEvents_; + unsigned int canvasWidth_; + unsigned int canvasHeight_; + + static EM_BOOL OnRequestAnimationFrame(double time, void *userData); + + static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); + + static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + protected: + void Invalidate(); + + void ClearCompositor() + { + compositor_.reset(); + } + + bool HasCompositor() const + { + return compositor_.get() != NULL; + } + + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + + virtual void Paint(ICompositor& compositor, + ViewportController& controller) = 0; + + /** + The second argument is temporary and should be deleted once the migration + to interactors is finished. It should be set to "true" for new applications. + */ + WebAssemblyViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + void PostConstructor(); + + void RefreshCanvasSize(); + + unsigned int GetCanvasWidth() const + { + return canvasWidth_; + } + + unsigned int GetCanvasHeight() + { + return canvasHeight_; + } + + public: + virtual ILock* Lock() ORTHANC_OVERRIDE; + + ~WebAssemblyViewport(); + + /** + This method takes ownership + */ + void AcquireInteractor(IViewportInteractor* interactor); + + const std::string& GetCanvasId() const + { + return canvasId_; + } + + /** + emscripten functions requires the css selector for the canvas. This is + different from the canvas id (the syntax is '#mycanvasid') + */ + const std::string& GetCanvasCssSelector() const + { + return canvasCssSelector_; + } + + void FitForPrint(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebGLViewport.cpp Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "WebGLViewport.h" + +#include "../../../OrthancStone/Sources/StoneException.h" +#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" + +namespace OrthancStone +{ + void WebGLViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + try + { + compositor.Refresh(controller.GetScene()); + + /** + * No need to manually swap the buffer: "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". + **/ + } + catch (const StoneException& e) + { + // Ignore problems about the loss of the WebGL context (edge case) + if (e.GetErrorCode() == ErrorCode_WebGLContextLost) + { + return; + } + else + { + throw; + } + } + } + + + WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) : + WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents), + context_(GetCanvasCssSelector()) + { + AcquireCompositor(new OpenGLCompositor(context_)); + } + + boost::shared_ptr<WebGLViewport> WebGLViewport::Create( + const std::string& canvasId, bool enableEmscriptenMouseEvents) + { + boost::shared_ptr<WebGLViewport> that = boost::shared_ptr<WebGLViewport>( + new WebGLViewport(canvasId, enableEmscriptenMouseEvents)); + + that->WebAssemblyViewport::PostConstructor(); + return that; + } + + WebGLViewport::~WebGLViewport() + { + // Make sure to delete the compositor before its parent "context_" gets + // deleted + ClearCompositor(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebGLViewport.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "WebAssemblyOpenGLContext.h" +#include "WebAssemblyViewport.h" + +namespace OrthancStone +{ + class WebGLViewport : public WebAssemblyViewport + { + private: + OpenGL::WebAssemblyOpenGLContext context_; + + WebGLViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr<WebGLViewport> Create(const std::string& canvasId, + bool enableEmscriptenMouseEvents = true); + + virtual ~WebGLViewport(); + + bool IsContextLost() + { + return context_.IsContextLost(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,221 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "WebGLViewportsRegistry.h" + +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +#include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" + +#include <OrthancException.h> + +#include <boost/make_shared.hpp> + + +static double viewportsTimeout_ = 1000; +static std::unique_ptr<OrthancStone::WebGLViewportsRegistry> globalRegistry_; + + +namespace OrthancStone +{ + void WebGLViewportsRegistry::LaunchTimer() + { + timeOutID_ = emscripten_set_timeout( + OnTimeoutCallback, + timeoutMS_, + reinterpret_cast<void*>(this)); + } + + void WebGLViewportsRegistry::OnTimeout() + { + for (Viewports::iterator it = viewports_.begin(); + it != viewports_.end(); + ++it) + { + if (it->second == NULL || + it->second->IsContextLost()) + { + LOG(INFO) << "WebGL context lost for canvas: " << it->first; + + // Try and duplicate the HTML5 canvas in the DOM + EM_ASM({ + var canvas = document.getElementById(UTF8ToString($0)); + if (canvas) { + var parent = canvas.parentElement; + if (parent) { + var cloned = canvas.cloneNode(true /* deep copy */); + parent.insertBefore(cloned, canvas); + parent.removeChild(canvas); + } + } + }, + it->first.c_str() // $0 = ID of the canvas + ); + + // At this point, the old canvas is removed from the DOM and + // replaced by a fresh one with the same ID: Recreate the + // WebGL context on the new canvas + boost::shared_ptr<WebGLViewport> viewport; + + // we need to steal the properties from the old viewport + // and set them to the new viewport + { + std::unique_ptr<IViewport::ILock> lock(it->second->Lock()); + + // TODO: remove ViewportController + Scene2D* scene = lock->GetController().ReleaseScene(); + viewport = WebGLViewport::Create(it->first); + + { + std::unique_ptr<IViewport::ILock> newLock(viewport->Lock()); + newLock->GetController().AcquireScene(scene); + } + } + + // Replace the old WebGL viewport by the new one + it->second = viewport; + + // Tag the fresh canvas as needing a repaint + { + std::unique_ptr<IViewport::ILock> lock(it->second->Lock()); + lock->Invalidate(); + } + } + } + + LaunchTimer(); + } + + void WebGLViewportsRegistry::OnTimeoutCallback(void *userData) + { + // This object dies with the process or tab. + WebGLViewportsRegistry* that = + reinterpret_cast<WebGLViewportsRegistry*>(userData); + that->OnTimeout(); + } + + WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) : + timeoutMS_(timeoutMS), + timeOutID_(0) + { + if (timeoutMS <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + LaunchTimer(); + } + + WebGLViewportsRegistry::~WebGLViewportsRegistry() + { + emscripten_clear_timeout(timeOutID_); + Clear(); + } + + boost::shared_ptr<WebGLViewport> WebGLViewportsRegistry::Add( + const std::string& canvasId) + { + if (viewports_.find(canvasId) != viewports_.end()) + { + LOG(ERROR) << "Canvas was already registered: " << canvasId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + boost::shared_ptr<WebGLViewport> viewport = + WebGLViewport::Create(canvasId); + viewports_[canvasId] = viewport; + return viewport; + } + } + + void WebGLViewportsRegistry::Remove(const std::string& canvasId) + { + Viewports::iterator found = viewports_.find(canvasId); + + if (found == viewports_.end()) + { + LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId; + } + else + { + viewports_.erase(found); + } + } + + void WebGLViewportsRegistry::Clear() + { + viewports_.clear(); + } + + WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId) : + that_(that) + { + Viewports::iterator viewport = that.viewports_.find(canvasId); + if (viewport != that.viewports_.end() && + viewport->second != NULL) + { + lock_.reset(viewport->second->Lock()); + } + } + + IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const + { + if (IsValid()) + { + return *lock_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void WebGLViewportsRegistry::FinalizeGlobalRegistry() + { + globalRegistry_.reset(); + } + + + void WebGLViewportsRegistry::SetGlobalRegistryTimeout(double timeout) + { + if (globalRegistry_.get()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + viewportsTimeout_ = timeout; + } + } + + + WebGLViewportsRegistry& WebGLViewportsRegistry::GetGlobalRegistry() + { + if (globalRegistry_.get() == NULL) + { + globalRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_)); + } + + return *globalRegistry_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.h Fri Oct 23 13:15:03 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "WebGLViewport.h" + +#include <boost/enable_shared_from_this.hpp> + +namespace OrthancStone +{ + /** + * This singleton class must be used if many WebGL viewports are + * created by the higher-level application, implying possible loss + * of WebGL contexts. The object will run an infinite update loop + * that checks whether all the WebGL context are still valid (not + * lost). If some WebGL context is lost, it is automatically + * reinitialized by created a fresh HTML5 canvas. + **/ + class WebGLViewportsRegistry : public boost::noncopyable, + public boost::enable_shared_from_this<WebGLViewportsRegistry> + { + private: + typedef std::map<std::string, boost::shared_ptr<WebGLViewport> > Viewports; + + double timeoutMS_; + Viewports viewports_; + long timeOutID_; + + void LaunchTimer(); + + void OnTimeout(); + + static void OnTimeoutCallback(void *userData); + + public: + explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */); + + ~WebGLViewportsRegistry(); + + boost::shared_ptr<WebGLViewport> Add(const std::string& canvasId); + + void Remove(const std::string& canvasId); + + void Clear(); + + class Accessor : public boost::noncopyable + { + private: + WebGLViewportsRegistry& that_; + std::unique_ptr<IViewport::ILock> lock_; + + public: + Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId); + + bool IsValid() const + { + return lock_.get() != NULL; + } + + IViewport::ILock& GetViewport() const; + }; + + + + static void FinalizeGlobalRegistry(); + + static void SetGlobalRegistryTimeout(double timeout); + + static WebGLViewportsRegistry& GetGlobalRegistry(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/LoaderCache.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,260 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "LoaderCache.h" + +#include "../StoneException.h" +#include "OrthancSeriesVolumeProgressiveLoader.h" +#include "OrthancMultiframeVolumeLoader.h" +#include "DicomStructureSetLoader.h" +#include "../Toolbox/GenericToolbox.h" + +#include "../Loaders/ILoadersContext.h" + +#if ORTHANC_ENABLE_WASM == 1 +# include <unistd.h> +# include "../Oracle/WebAssemblyOracle.h" +#else +# include "../Oracle/ThreadedOracle.h" +#endif + +#include "../Volumes/DicomVolumeImage.h" +#include "../Volumes/DicomVolumeImageMPRSlicer.h" + +#include <OrthancException.h> +#include <Toolbox.h> + +namespace OrthancStone +{ + LoaderCache::LoaderCache(OrthancStone::ILoadersContext& loadersContext, bool useCtProgressiveQuality) + : loadersContext_(loadersContext) + , useCtProgressiveQuality_(useCtProgressiveQuality) + + { + + } + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> + LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid) + { + try + { + // normalize keys a little + GenericToolbox::NormalizeUuid(seriesUuid); + + // find in cache + if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end()) + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); + + boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader; + + // true means "use progressive quality" + // false means "load high quality slices only" + loader = OrthancSeriesVolumeProgressiveLoader::Create(loadersContext_, volumeImage, useCtProgressiveQuality_); + loader->LoadSeries(seriesUuid); + seriesVolumeProgressiveLoaders_[seriesUuid] = loader; + } + else + { +// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid; + } + return seriesVolumeProgressiveLoaders_[seriesUuid]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + + boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid) + { + // normalize keys a little + GenericToolbox::NormalizeUuid(instanceUuid); + + // if the loader is not available, let's trigger its creation + if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end()) + { + GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid); + } + ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end()); + + return multiframeVolumeLoaders_[instanceUuid]; + } + + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) + { + try + { + // normalize keys a little + GenericToolbox::NormalizeUuid(instanceUuid); + + // find in cache + if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end()) + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); + boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); + boost::shared_ptr<OrthancMultiframeVolumeLoader> loader; + { + loader = OrthancMultiframeVolumeLoader::Create(loadersContext_, volumeImage); + loader->LoadInstance(instanceUuid); + } + multiframeVolumeLoaders_[instanceUuid] = loader; + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage)); + dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer; + } + return dicomVolumeImageMPRSlicers_[instanceUuid]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + + std::string LoaderCache::BuildDicomStructureSetLoaderKey( + const std::string& instanceUuid, + const std::string& uniqueKey) + { + return instanceUuid + "_" + uniqueKey; + } + + boost::shared_ptr<DicomStructureSetLoader> LoaderCache::GetDicomStructureSetLoader( + std::string inInstanceUuid, + const std::vector<std::string>& initiallyVisibleStructures, + const std::string& uniqueKey) + { + try + { + // normalize keys a little + GenericToolbox::NormalizeUuid(inInstanceUuid); + + std::string entryKey = BuildDicomStructureSetLoaderKey(inInstanceUuid, uniqueKey); + + // find in cache + if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end()) + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); + + boost::shared_ptr<DicomStructureSetLoader> loader; + { + loader = DicomStructureSetLoader::Create(loadersContext_); + loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); + } + dicomStructureSetLoaders_[entryKey] = loader; + } + return dicomStructureSetLoaders_[entryKey]; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); + } + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in LoaderCache"; + throw; + } + } + + void LoaderCache::ClearCache() + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); + +#ifndef NDEBUG + // ISO way of checking for debug builds + DebugDisplayObjRefCounts(); +#endif + seriesVolumeProgressiveLoaders_.clear(); + multiframeVolumeLoaders_.clear(); + dicomVolumeImageMPRSlicers_.clear(); + dicomStructureSetLoaders_.clear(); + + } + + template<typename T> void DebugDisplayObjRefCountsInMap( + const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap) + { + LOG(TRACE) << "Map \"" << name << "\" ref counts:"; + size_t i = 0; + for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator + it = myMap.begin(); it != myMap.end(); ++it) + { + LOG(TRACE) << " element #" << i << ": ref count = " << it->second.use_count(); + i++; + } + } + + void LoaderCache::DebugDisplayObjRefCounts() + { + DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_); + DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_); + DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_); + DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/LoaderCache.h Fri Oct 23 13:15:03 2020 +0200 @@ -0,0 +1,102 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include "../Volumes/DicomVolumeImageMPRSlicer.h" +#include "OrthancSeriesVolumeProgressiveLoader.h" +#include "OrthancMultiframeVolumeLoader.h" +#include "DicomStructureSetLoader.h" + +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace OrthancStone +{ + class ILoadersContext; +} + +namespace OrthancStone +{ + class LoaderCache + { + public: + + virtual ~LoaderCache() {} + + /** + By default, the CT loader in loader cache will only download the highest quality slices. + If you pass true for useCtProgressiveQuality, jpeg (50/100 quality), then jpeg (90/100 quality) + then eventually uncompressed 16-bit images will be loaded. + */ + LoaderCache(OrthancStone::ILoadersContext& loadersContext, + bool useCtProgressiveQuality); + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> + GetSeriesVolumeProgressiveLoader (std::string seriesUuid); + + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> + GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid); + + boost::shared_ptr<OrthancMultiframeVolumeLoader> + GetMultiframeVolumeLoader(std::string instanceUuid); + + /** + The DicomStructureSetLoader instances are stored in a map and indexed + by a key built from instanceUuid and uniqueKey. + + If instanceUuid and uniqueKey correspond to an already existing loader, it is returned. + + Please note that initiallyVisibleStructures is only used if the call results in the creation + of a new loader. In that case, the value is passed to the constructor. + */ + boost::shared_ptr<DicomStructureSetLoader> + GetDicomStructureSetLoader( + std::string instanceUuid, + const std::vector<std::string>& initiallyVisibleStructures, + const std::string& uniqueKey = ""); + + std::string BuildDicomStructureSetLoaderKey( + const std::string& instanceUuid, + const std::string& uniqueKey = ""); + + void ClearCache(); + + protected: + + void DebugDisplayObjRefCounts(); + + OrthancStone::ILoadersContext& loadersContext_; + bool useCtProgressiveQuality_; + + std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> > + seriesVolumeProgressiveLoaders_; + std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> > + multiframeVolumeLoaders_; + std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> > + dicomVolumeImageMPRSlicers_; + std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> > + dicomStructureSetLoaders_; + }; +} +
--- a/Applications/Samples/Sdl/CMakeLists.txt Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/Sdl/CMakeLists.txt Fri Oct 23 13:15:03 2020 +0200 @@ -21,7 +21,7 @@ project(OrthancStone) -include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/Sdl/OrthancStoneSdlParameters.cmake) if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") set(ORTHANC_BOOST_COMPONENTS program_options) @@ -43,7 +43,7 @@ set(ENABLE_SDL ON) SET(ENABLE_PUGIXML ON) # To test compilation of OsiriX annotations -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/Sdl/OrthancStoneSdlConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Utilities.cmake) if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
--- a/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -27,9 +27,9 @@ // Stone of Orthanc includes #include "../../../../OrthancStone/Sources/Loaders/GenericLoadersContext.h" #include "../../../../OrthancStone/Sources/OpenGL/OpenGLIncludes.h" -#include "../../../../OrthancStone/Sources/OpenGL/SdlOpenGLContext.h" #include "../../../../OrthancStone/Sources/StoneException.h" #include "../../../../OrthancStone/Sources/StoneInitialization.h" +#include "../../../Platforms/Sdl/SdlOpenGLContext.h" // Orthanc (a.o. for screenshot capture) #include <Compatibility.h> // For std::unique_ptr<> @@ -451,6 +451,7 @@ using namespace OrthancStone; StoneInitialize(); + OrthancStone::SdlWindow::GlobalInitialize(); try { @@ -462,7 +463,8 @@ { LOG(ERROR) << "EXCEPTION: " << e.What(); } - + + OrthancStone::SdlWindow::GlobalFinalize(); StoneFinalize(); return 0;
--- a/Applications/Samples/Sdl/SdlHelpers.h Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/Sdl/SdlHelpers.h Fri Oct 23 13:15:03 2020 +0200 @@ -25,7 +25,7 @@ # error This file cannot be used if ORTHANC_ENABLE_SDL != 1 #endif -#include "../../../OrthancStone/Sources/Viewport/SdlViewport.h" +#include "../../Platforms/Sdl/SdlViewport.h" #include <boost/shared_ptr.hpp>
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -28,7 +28,7 @@ #include "../../../../OrthancStone/Sources/StoneException.h" #include "../../../../OrthancStone/Sources/StoneInitialization.h" #include "../../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h" -#include "../../../../OrthancStone/Sources/Viewport/SdlViewport.h" +#include "../../../Platforms/Sdl/SdlViewport.h" #include <Compatibility.h> // For std::unique_ptr<> #include <OrthancException.h> @@ -110,7 +110,8 @@ try { OrthancStone::StoneInitialize(); - + OrthancStone::SdlWindow::GlobalInitialize(); + ProcessOptions(argc, argv); //Orthanc::Logging::EnableInfoLevel(true); @@ -252,6 +253,7 @@ } } + OrthancStone::SdlWindow::GlobalFinalize(); OrthancStone::StoneFinalize(); return 0; }
--- a/Applications/Samples/WebAssembly/CMakeLists.txt Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/WebAssembly/CMakeLists.txt Fri Oct 23 13:15:03 2020 +0200 @@ -49,17 +49,16 @@ # --------------------------------------------------------------- set(ALLOW_DOWNLOADS ON) -include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake) SET(ENABLE_DCMTK OFF) # Not necessary SET(ENABLE_GOOGLE_TEST OFF) SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) SET(ORTHANC_SANDBOXED ON) # this will set up the build system for Stone of Orthanc and will # populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake) # We embed a font to be used for on-screen overlays
--- a/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -23,11 +23,10 @@ #include "../../Common/SampleHelpers.h" // Stone of Orthanc includes -#include "../../../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h" #include "../../../../OrthancStone/Sources/StoneException.h" #include "../../../../OrthancStone/Sources/StoneInitialization.h" -#include "../../../../OrthancStone/Sources/Viewport/WebGLViewport.h" -//#include "../../../../OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.h" +#include "../../../Platforms/WebAssembly/WebAssemblyLoadersContext.h" +#include "../../../Platforms/WebAssembly/WebGLViewport.h" #include <Toolbox.h>
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -21,9 +21,11 @@ #include "SingleFrameViewerApplication.h" -#include "../../../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h" #include "../../../../OrthancStone/Sources/StoneException.h" #include "../../../../OrthancStone/Sources/StoneInitialization.h" +#include "../../../Platforms/WebAssembly/WebGLViewport.h" +#include "../../../Platforms/WebAssembly/WebAssemblyLoadersContext.h" +#include "../../../Platforms/WebAssembly/WebGLViewportsRegistry.h" #include <Compatibility.h> // For std::unique_ptr<> #include <Toolbox.h> @@ -114,12 +116,13 @@ "Only one single viewport is available for this application"); } - boost::shared_ptr<OrthancStone::WebGLViewport> viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); + boost::shared_ptr<OrthancStone::WebGLViewport> viewport( + OrthancStone::WebGLViewportsRegistry::GetGlobalRegistry().Add(canvasId)); application_ = OrthancStone::Application::Create(*context_, viewport); { OrthancStone::WebGLViewportsRegistry::Accessor accessor( - OrthancStone::GetWebGLViewportsRegistry(), canvasId); + OrthancStone::WebGLViewportsRegistry::GetGlobalRegistry(), canvasId); if (accessor.IsValid()) {
--- a/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Fri Oct 23 13:15:03 2020 +0200 @@ -49,23 +49,23 @@ # --------------------------------------------------------------- set(ALLOW_DOWNLOADS ON) -include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake) SET(ENABLE_DCMTK ON) SET(ENABLE_DCMTK_NETWORKING OFF) SET(ENABLE_DCMTK_TRANSCODING OFF) SET(ENABLE_GOOGLE_TEST OFF) SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) SET(ENABLE_PUGIXML ON) # Necessary for OsiriX annotations SET(ORTHANC_SANDBOXED ON) # this will set up the build system for Stone of Orthanc and will # populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake) include_directories( ${CMAKE_SOURCE_DIR}/../../../OrthancStone/Sources/ + ${CMAKE_SOURCE_DIR}/../../Platforms/WebAssembly/ ) if (CMAKE_BUILD_TYPE MATCHES Debug)
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -66,7 +66,6 @@ #include <Loaders/DicomResourcesLoader.h> #include <Loaders/SeriesMetadataLoader.h> #include <Loaders/SeriesThumbnailsLoader.h> -#include <Loaders/WebAssemblyLoadersContext.h> #include <Messages/ObserverBase.h> #include <Oracle/ParseDicomFromWadoCommand.h> #include <Oracle/ParseDicomSuccessMessage.h> @@ -79,8 +78,12 @@ #include <Toolbox/GeometryToolbox.h> #include <Toolbox/SortedFrames.h> #include <Viewport/DefaultViewportInteractor.h> -#include <Viewport/WebAssemblyCairoViewport.h> -#include <Viewport/WebGLViewport.h> + +// WebAssembly includes +#include <WebAssemblyCairoViewport.h> +#include <WebAssemblyLoadersContext.h> +#include <WebGLViewport.h> + #include <boost/make_shared.hpp> #include <stdio.h>
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Fri Oct 23 13:15:03 2020 +0200 @@ -56,45 +56,11 @@ message(FATAL_ERROR "Cannot enable curl in sandboxed environments") endif() - if (ENABLE_SDL) - message(FATAL_ERROR "Cannot enable SDL in sandboxed environments") - endif() - if (ENABLE_SSL) message(FATAL_ERROR "Cannot enable SSL in sandboxed environments") endif() endif() -if (ENABLE_OPENGL) - if (NOT ENABLE_SDL AND NOT ENABLE_WASM) - message(FATAL_ERROR "Cannot enable OpenGL if WebAssembly and SDL are both disabled") - 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() - - set(ENABLE_THREADS OFF) - set(ENABLE_WEB_CLIENT OFF) - 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() - - set(ENABLE_THREADS ON) - add_definitions(-DORTHANC_ENABLE_WASM=0) -endif() - ##################################################################### ## Configure mandatory third-party components @@ -118,21 +84,6 @@ endif() -if(ENABLE_SDL) - message("SDL is enabled") - include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) - add_definitions( - -DORTHANC_ENABLE_SDL=1 - ) -else() - message("SDL is disabled") - unset(USE_SYSTEM_SDL CACHE) - add_definitions( - -DORTHANC_ENABLE_SDL=0 - ) -endif() - - if (ENABLE_THREADS) add_definitions(-DORTHANC_ENABLE_THREADS=1) else() @@ -200,13 +151,6 @@ ## System-specific patches ##################################################################### -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND - NOT MSVC AND - ENABLE_SDL) - # This is necessary when compiling EXE for Windows using MinGW - link_libraries(mingw32) -endif() - if (ORTHANC_SANDBOXED) # Remove functions not suitable for a sandboxed environment list(REMOVE_ITEM ORTHANC_CORE_SOURCES @@ -222,23 +166,6 @@ ## All the source files required to build Stone of Orthanc ##################################################################### -if (ENABLE_SDL) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlWindow.h - ) - - if (ENABLE_OPENGL) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.cpp - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/SdlOpenGLContext.h - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/SdlViewport.h - ) - endif() -endif() - - if (ENABLE_DCMTK) list(APPEND ORTHANC_STONE_SOURCES ${ORTHANC_STONE_ROOT}/Sources/Oracle/ParseDicomSuccessMessage.cpp @@ -259,17 +186,6 @@ endif() -if (ENABLE_WASM) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/Loaders/WebAssemblyLoadersContext.cpp - ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.h - ) -endif() - - if (ENABLE_PUGIXML) list(APPEND ORTHANC_STONE_SOURCES ${ORTHANC_STONE_ROOT}/Sources/Toolbox/OsiriX/AngleAnnotation.cpp @@ -310,8 +226,6 @@ ${ORTHANC_STONE_ROOT}/Sources/Loaders/IFetchingItemsSorter.h ${ORTHANC_STONE_ROOT}/Sources/Loaders/IFetchingStrategy.h ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoadedDicomResources.cpp - ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderCache.cpp - ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderCache.h ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderStateMachine.cpp ${ORTHANC_STONE_ROOT}/Sources/Loaders/LoaderStateMachine.h ${ORTHANC_STONE_ROOT}/Sources/Loaders/OrthancMultiframeVolumeLoader.cpp @@ -533,8 +447,6 @@ ${PIXMAN_SOURCES} # Optional components - ${SDL_SOURCES} - ${QT_SOURCES} ${GLEW_SOURCES} ) @@ -578,16 +490,8 @@ ${ORTHANC_STONE_ROOT}/Sources/Scene2D/Internals/OpenGLTextureProgram.cpp ${ORTHANC_STONE_ROOT}/Sources/Scene2D/Internals/OpenGLTextureProgram.h ) +endif() - if (ENABLE_WASM) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/WebAssemblyOpenGLContext.cpp - ${ORTHANC_STONE_ROOT}/Sources/OpenGL/WebAssemblyOpenGLContext.h - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebGLViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebGLViewportsRegistry.cpp - ) - endif() -endif() ## ## TEST - Automatically add all ".h" headers to the list of sources
--- a/OrthancStone/Resources/CMake/OrthancStoneParameters.cmake Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Resources/CMake/OrthancStoneParameters.cmake Fri Oct 23 13:15:03 2020 +0200 @@ -74,12 +74,13 @@ ## CMake parameters tunable by the user ##################################################################### +set(ENABLE_OPENGL ON CACHE BOOL "Enable support of OpenGL") + # 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") @@ -88,5 +89,4 @@ ## the Stone of Orthanc ##################################################################### -set(ENABLE_OPENGL ON CACHE BOOL "Enable support of OpenGL") -set(ENABLE_WASM OFF CACHE INTERNAL "Enable support of WebAssembly") +set(ENABLE_THREADS ON CACHE INTERNAL "Enable threading support (must be false for WebAssembly)")
--- a/OrthancStone/Resources/CMake/SdlConfiguration.cmake Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,224 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero General Public License -# as published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -if (STATIC_BUILD OR NOT USE_SYSTEM_SDL) - 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) - - set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h") - if (NOT EXISTS "${TMP}") - file(WRITE "${TMP}" " -#include \"SDL_platform.h\" -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 -#define HAVE_STDINT_H 1 -") - endif() - - # General source files - file(GLOB SDL_SOURCES - ${SDL_SOURCES_DIR}/src/*.c - ${SDL_SOURCES_DIR}/src/atomic/*.c - ${SDL_SOURCES_DIR}/src/audio/*.c - ${SDL_SOURCES_DIR}/src/cpuinfo/*.c - ${SDL_SOURCES_DIR}/src/dynapi/*.c - ${SDL_SOURCES_DIR}/src/events/*.c - ${SDL_SOURCES_DIR}/src/file/*.c - ${SDL_SOURCES_DIR}/src/haptic/*.c - ${SDL_SOURCES_DIR}/src/joystick/*.c - ${SDL_SOURCES_DIR}/src/libm/*.c - ${SDL_SOURCES_DIR}/src/power/*.c - ${SDL_SOURCES_DIR}/src/render/*.c - ${SDL_SOURCES_DIR}/src/stdlib/*.c - ${SDL_SOURCES_DIR}/src/thread/*.c - ${SDL_SOURCES_DIR}/src/timer/*.c - ${SDL_SOURCES_DIR}/src/video/*.c - - ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c - #${SDL_SOURCES_DIR}/src/timer/dummy/*.c - ${SDL_SOURCES_DIR}/src/audio/dummy/*.c - ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c - ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c - ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c - #${SDL_SOURCES_DIR}/src/main/dummy/*.c - ${SDL_SOURCES_DIR}/src/video/dummy/*.c - ) - - add_definitions( - -DUSING_PREMAKE_CONFIG_H=1 - - -DSDL_AUDIO_DISABLED=1 - -DSDL_AUDIO_DRIVER_DUMMY=1 - -DSDL_FILESYSTEM_DISABLED=1 - -DSDL_FILESYSTEM_DUMMY=1 - -DSDL_FILE_DISABLED=1 - -DSDL_HAPTIC_DISABLED=1 - -DSDL_JOYSTICK_DISABLED=1 - - #-DSDL_THREADS_DISABLED=1 - ) - - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/core/linux/*.c - ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/pthread/*.c - ${SDL_SOURCES_DIR}/src/timer/unix/*.c - ${SDL_SOURCES_DIR}/src/video/x11/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_LOADSO_DLOPEN=1 - -DSDL_THREAD_PTHREAD=1 - -DSDL_TIMER_UNIX=1 - -DSDL_POWER_DISABLED=1 - - -DSDL_VIDEO_DRIVER_X11=1 - - -DSDL_ASSEMBLY_ROUTINES=1 - -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 - -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1 - -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1 - ) - - link_libraries(X11 Xext) - - if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry") - # Raspberry Pi has no support for OpenGL - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_ES2=1 - -DSDL_VIDEO_RENDER_OGL=1 - -DSDL_VIDEO_RENDER_OGL_ES2=1 - -DSDL_VIDEO_OPENGL_GLX=1 - -DSDL_VIDEO_OPENGL_EGL=1 - ) - endif() - - elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/audio/directsound/*.c - ${SDL_SOURCES_DIR}/src/audio/disk/*.c - ${SDL_SOURCES_DIR}/src/audio/winmm/*.c - ${SDL_SOURCES_DIR}/src/joystick/windows/*.c - ${SDL_SOURCES_DIR}/src/haptic/windows/*.c - ${SDL_SOURCES_DIR}/src/power/windows/*.c - - ${SDL_SOURCES_DIR}/src/main/windows/*.c - ${SDL_SOURCES_DIR}/src/core/windows/*.c - ${SDL_SOURCES_DIR}/src/loadso/windows/*.c - ${SDL_SOURCES_DIR}/src/render/direct3d/*.c - ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/psp/*.c - ${SDL_SOURCES_DIR}/src/render/opengles/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c # Don't include more files from "thread/generic/*.c"! - ${SDL_SOURCES_DIR}/src/thread/windows/*.c - ${SDL_SOURCES_DIR}/src/timer/windows/*.c - ${SDL_SOURCES_DIR}/src/video/windows/*.c - ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - # NB: OpenGL ES headers are not available in MinGW-W64 - add_definitions( - -DSDL_LOADSO_WINDOWS=1 - -DSDL_THREAD_WINDOWS=1 - -DSDL_TIMER_WINDOWS=1 - -DSDL_POWER_WINDOWS=1 - - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_WGL=1 - -DSDL_VIDEO_RENDER_D3D=1 - -DSDL_VIDEO_RENDER_OGL=1 - -DSDL_VIDEO_DRIVER_WINDOWS=1 - ) - - if (MSVC) - add_definitions( - -D__FLTUSED__ - -DHAVE_LIBC=1 - ) - else() - add_definitions( - -DHAVE_GCC_ATOMICS=1 - -DSDL_ASSEMBLY_ROUTINES=1 - ) - endif() - - link_libraries(imm32 winmm version) - - elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/pthread/*.c - ${SDL_SOURCES_DIR}/src/timer/unix/*.c - ${SDL_SOURCES_DIR}/src/video/cocoa/*.m - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_LOADSO_DLOPEN=1 - -DSDL_THREAD_PTHREAD=1 - -DSDL_TIMER_UNIX=1 - -DSDL_POWER_DISABLED=1 - - -DSDL_VIDEO_DRIVER_COCOA=1 - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_CGL=1 - -DSDL_VIDEO_RENDER_OGL=1 - - -DSDL_ASSEMBLY_ROUTINES=1 - -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 - ) - - find_library(CARBON_LIBRARY Carbon) - find_library(COCOA_LIBRARY Cocoa) - find_library(IOKIT_LIBRARY IOKit) - find_library(QUARTZ_LIBRARY QuartzCore) - link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY}) - - endif() - -else() - pkg_search_module(SDL2 REQUIRED sdl2) - include_directories(${SDL2_INCLUDE_DIRS}) - link_libraries(${SDL2_LIBRARIES}) -endif()
--- a/OrthancStone/SharedLibrary/WebAssembly/CMakeLists.txt Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,212 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero General Public License -# as published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -cmake_minimum_required(VERSION 2.8.3) - -project(OrthancStoneModule) - - -# Warning message related to WebAssembly modules: We know that 1.38.41 -# DOES NOT work, but that 1.39.17 works. -message("") -message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===") -message("") - - -set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries") - - -# Ask for the generation of a side module -set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1") # Must be before "Compiler.cmake" - - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- - -set(USE_WASM ON CACHE BOOL "") - -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") - -set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - - -# Stone of Orthanc configuration -# --------------------------------------------------------------- - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ENABLE_DCMTK ON) -SET(ENABLE_DCMTK_NETWORKING OFF) -SET(ENABLE_DCMTK_TRANSCODING OFF) -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) -SET(ORTHANC_SANDBOXED ON) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) - - - - -################################################################################ - -# The source files that register a callback cannot be part of a side -# module, and must be compiled in the main module. The following -# command can be used to identify such files: -# $ grep -lrE 'emscripten_' ../../Sources/ - -set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS - ${ORTHANC_STONE_ROOT}/Sources/Oracle/WebAssemblyOracle.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyViewport.cpp - ${ORTHANC_STONE_ROOT}/Sources/Viewport/WebAssemblyCairoViewport.cpp - ) - -list(REMOVE_ITEM ORTHANC_STONE_SOURCES - ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} - ) - -configure_file( - ${CMAKE_SOURCE_DIR}/../OrthancStone.h.in - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h - ) - -configure_file( - ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h - ) - -file( - COPY ${CMAKE_SOURCE_DIR}/../../Sources/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN OrthancStone.h EXCLUDE - PATTERN "Deprecated*" EXCLUDE - ) - -file( - COPY ${ORTHANC_FRAMEWORK_ROOT}/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN OrthancFramework.h EXCLUDE - ) - -add_executable(OrthancStoneModule - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ${CAIRO_SOURCES} - ${PIXMAN_SOURCES} - ${FREETYPE_SOURCES} - ) - -set_target_properties(OrthancStoneModule - PROPERTIES - COMPILE_FLAGS "${WASM_FLAGS}" - LINK_FLAGS "${WASM_LINKER_FLAGS}" - ) - -# CMake does not natively handle SIDE_MODULE, and believes that -# Emscripten produces a ".js" file (whereas it creates only the -# ".wasm"). Create a dummy ".js" for target to work. -add_custom_command( - TARGET OrthancStoneModule POST_BUILD - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js - ) - -file( - COPY ${BOOST_SOURCES_DIR}/boost/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "*.ipp" - ) - -file( - COPY ${JSONCPP_SOURCES_DIR}/include/json/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) - -file( - COPY ${CAIRO_SOURCES_DIR}/src/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) - -set(DCMTK_MODULES - dcmdata - config - ofstd - oflog - ) - -foreach (module IN LISTS DCMTK_MODULES) - file( - COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) -endforeach() - - -install( - TARGETS OrthancStoneModule - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib - ) - -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib - ) - -install( - DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR}/Include/boost - ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo - ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk - ${CMAKE_CURRENT_BINARY_DIR}/Include/json - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/ - ) - -install(FILES - ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone - )
--- a/OrthancStone/SharedLibrary/WebAssembly/NOTES.txt Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ - -Install Emscripten: -https://emscripten.org/docs/getting_started/downloads.html - -# cd ~/Downloads -# git clone https://github.com/emscripten-core/emsdk.git -# cd emsdk -# ./emsdk install 2.0.0 -# ./emsdk activate 2.0.0 - - -Then, if the installation path was "~/Downloads/emsdk/": - -# source ~/Downloads/emsdk/emsdk_env.sh -# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja -# ninja install - -=> The binaries will be put in "../../../../wasm-binaries/"
--- a/OrthancStone/Sources/Loaders/LoaderCache.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - -#include "LoaderCache.h" - -#include "../StoneException.h" -#include "OrthancSeriesVolumeProgressiveLoader.h" -#include "OrthancMultiframeVolumeLoader.h" -#include "DicomStructureSetLoader.h" - -#include "../Loaders/ILoadersContext.h" - -#if ORTHANC_ENABLE_WASM == 1 -# include <unistd.h> -# include "../Oracle/WebAssemblyOracle.h" -#else -# include "../Oracle/ThreadedOracle.h" -#endif - -#include "../Volumes/DicomVolumeImage.h" -#include "../Volumes/DicomVolumeImageMPRSlicer.h" - -#include <OrthancException.h> -#include <Toolbox.h> - -namespace OrthancStone -{ - LoaderCache::LoaderCache(OrthancStone::ILoadersContext& loadersContext, bool useCtProgressiveQuality) - : loadersContext_(loadersContext) - , useCtProgressiveQuality_(useCtProgressiveQuality) - - { - - } - - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> - LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid) - { - try - { - // normalize keys a little - NormalizeUuid(seriesUuid); - - // find in cache - if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end()) - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); - - boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> loader; - - // true means "use progressive quality" - // false means "load high quality slices only" - loader = OrthancSeriesVolumeProgressiveLoader::Create(loadersContext_, volumeImage, useCtProgressiveQuality_); - loader->LoadSeries(seriesUuid); - seriesVolumeProgressiveLoaders_[seriesUuid] = loader; - } - else - { -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : returning cached loader for seriesUUid = " << seriesUuid; - } - return seriesVolumeProgressiveLoaders_[seriesUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - - boost::shared_ptr<OrthancMultiframeVolumeLoader> LoaderCache::GetMultiframeVolumeLoader(std::string instanceUuid) - { - // normalize keys a little - NormalizeUuid(instanceUuid); - - // if the loader is not available, let's trigger its creation - if(multiframeVolumeLoaders_.find(instanceUuid) == multiframeVolumeLoaders_.end()) - { - GetMultiframeDicomVolumeImageMPRSlicer(instanceUuid); - } - ORTHANC_ASSERT(multiframeVolumeLoaders_.find(instanceUuid) != multiframeVolumeLoaders_.end()); - - return multiframeVolumeLoaders_[instanceUuid]; - } - - boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) - { - try - { - // normalize keys a little - NormalizeUuid(instanceUuid); - - // find in cache - if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end()) - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); - boost::shared_ptr<OrthancStone::DicomVolumeImage> volumeImage(new OrthancStone::DicomVolumeImage); - boost::shared_ptr<OrthancMultiframeVolumeLoader> loader; - { - loader = OrthancMultiframeVolumeLoader::Create(loadersContext_, volumeImage); - loader->LoadInstance(instanceUuid); - } - multiframeVolumeLoaders_[instanceUuid] = loader; - boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage)); - dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer; - } - return dicomVolumeImageMPRSlicers_[instanceUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - - std::string LoaderCache::BuildDicomStructureSetLoaderKey( - const std::string& instanceUuid, - const std::string& uniqueKey) - { - return instanceUuid + "_" + uniqueKey; - } - - boost::shared_ptr<DicomStructureSetLoader> LoaderCache::GetDicomStructureSetLoader( - std::string inInstanceUuid, - const std::vector<std::string>& initiallyVisibleStructures, - const std::string& uniqueKey) - { - try - { - // normalize keys a little - NormalizeUuid(inInstanceUuid); - - std::string entryKey = BuildDicomStructureSetLoaderKey(inInstanceUuid, uniqueKey); - - // find in cache - if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end()) - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); - - boost::shared_ptr<DicomStructureSetLoader> loader; - { - loader = DicomStructureSetLoader::Create(loadersContext_); - loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); - } - dicomStructureSetLoaders_[entryKey] = loader; - } - return dicomStructureSetLoaders_[entryKey]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in LoaderCache: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in LoaderCache: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in LoaderCache"; - throw; - } - } - - void LoaderCache::ClearCache() - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(loadersContext_.Lock()); - -#ifndef NDEBUG - // ISO way of checking for debug builds - DebugDisplayObjRefCounts(); -#endif - seriesVolumeProgressiveLoaders_.clear(); - multiframeVolumeLoaders_.clear(); - dicomVolumeImageMPRSlicers_.clear(); - dicomStructureSetLoaders_.clear(); - - } - - template<typename T> void DebugDisplayObjRefCountsInMap( - const std::string& name, const std::map<std::string, boost::shared_ptr<T> >& myMap) - { - LOG(TRACE) << "Map \"" << name << "\" ref counts:"; - size_t i = 0; - for (typename std::map<std::string, boost::shared_ptr<T> >::const_iterator - it = myMap.begin(); it != myMap.end(); ++it) - { - LOG(TRACE) << " element #" << i << ": ref count = " << it->second.use_count(); - i++; - } - } - - void LoaderCache::DebugDisplayObjRefCounts() - { - DebugDisplayObjRefCountsInMap("seriesVolumeProgressiveLoaders_", seriesVolumeProgressiveLoaders_); - DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_); - DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_); - DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_); - } - - /** - This method could have been called StripSpacesAndChangeToLower but we might want to - add some UUID validation to the argument - */ - void LoaderCache::NormalizeUuid(std::string& uuid) - { - std::string temp = Orthanc::Toolbox::StripSpaces(uuid); - Orthanc::Toolbox::ToLowerCase(temp); - uuid.swap(temp); - } -}
--- a/OrthancStone/Sources/Loaders/LoaderCache.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - -#pragma once - -#include "../Volumes/DicomVolumeImageMPRSlicer.h" -#include "OrthancSeriesVolumeProgressiveLoader.h" -#include "OrthancMultiframeVolumeLoader.h" -#include "DicomStructureSetLoader.h" - -#include <boost/shared_ptr.hpp> - -#include <map> -#include <string> -#include <vector> - -namespace OrthancStone -{ - class ILoadersContext; -} - -namespace OrthancStone -{ - class LoaderCache - { - public: - - virtual ~LoaderCache() {} - - /** - By default, the CT loader in loader cache will only download the highest quality slices. - If you pass true for useCtProgressiveQuality, jpeg (50/100 quality), then jpeg (90/100 quality) - then eventually uncompressed 16-bit images will be loaded. - */ - LoaderCache(OrthancStone::ILoadersContext& loadersContext, - bool useCtProgressiveQuality); - - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> - GetSeriesVolumeProgressiveLoader (std::string seriesUuid); - - boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> - GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid); - - boost::shared_ptr<OrthancMultiframeVolumeLoader> - GetMultiframeVolumeLoader(std::string instanceUuid); - - /** - The DicomStructureSetLoader instances are stored in a map and indexed - by a key built from instanceUuid and uniqueKey. - - If instanceUuid and uniqueKey correspond to an already existing loader, it is returned. - - Please note that initiallyVisibleStructures is only used if the call results in the creation - of a new loader. In that case, the value is passed to the constructor. - */ - boost::shared_ptr<DicomStructureSetLoader> - GetDicomStructureSetLoader( - std::string instanceUuid, - const std::vector<std::string>& initiallyVisibleStructures, - const std::string& uniqueKey = ""); - - std::string BuildDicomStructureSetLoaderKey( - const std::string& instanceUuid, - const std::string& uniqueKey = ""); - - void ClearCache(); - - /** - Service method static and exposed for unit tests. - */ - static void NormalizeUuid(std::string& uuid); - - protected: - - void DebugDisplayObjRefCounts(); - - OrthancStone::ILoadersContext& loadersContext_; - bool useCtProgressiveQuality_; - - std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> > - seriesVolumeProgressiveLoaders_; - std::map<std::string, boost::shared_ptr<OrthancMultiframeVolumeLoader> > - multiframeVolumeLoaders_; - std::map<std::string, boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> > - dicomVolumeImageMPRSlicers_; - std::map<std::string, boost::shared_ptr<DicomStructureSetLoader> > - dicomStructureSetLoaders_; - }; -} -
--- a/OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "WebAssemblyLoadersContext.h" - -namespace OrthancStone -{ - class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock - { - private: - WebAssemblyLoadersContext& that_; - - public: - explicit Locker(WebAssemblyLoadersContext& that) : - that_(that) - { - } - - virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE - { - return that_; - } - - virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE - { - return that_.oracle_.GetOracleObservable(); - } - - virtual void Schedule(boost::shared_ptr<IObserver> receiver, - int priority, - IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE - { - that_.scheduler_->Schedule(receiver, priority, command); - } - - virtual void CancelRequests(boost::shared_ptr<IObserver> receiver) ORTHANC_OVERRIDE - { - that_.scheduler_->CancelRequests(receiver); - } - - virtual void CancelAllRequests() ORTHANC_OVERRIDE - { - that_.scheduler_->CancelAllRequests(); - } - - virtual void AddLoader(boost::shared_ptr<IObserver> loader) ORTHANC_OVERRIDE - { - that_.loaders_.push_back(loader); - } - - virtual void GetStatistics(uint64_t& scheduledCommands, - uint64_t& processedCommands) ORTHANC_OVERRIDE - { - scheduledCommands = that_.scheduler_->GetTotalScheduled(); - processedCommands = that_.scheduler_->GetTotalProcessed(); - } - }; - - - WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority, - unsigned int maxStandardPriority, - unsigned int maxLowPriority) - { - oracle_.GetOracleObservable(); - scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_, - maxHighPriority, maxStandardPriority, maxLowPriority); - - if (!scheduler_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - ILoadersContext::ILock* WebAssemblyLoadersContext::Lock() - { - return new Locker(*this); - } -}
--- a/OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ILoadersContext.h" -#include "../Oracle/WebAssemblyOracle.h" -#include "OracleScheduler.h" - -#include <list> - -namespace OrthancStone -{ - class WebAssemblyLoadersContext : public ILoadersContext - { - private: - class Locker; - - WebAssemblyOracle oracle_; - boost::shared_ptr<OracleScheduler> scheduler_; - std::list< boost::shared_ptr<IObserver> > loaders_; - - public: - WebAssemblyLoadersContext(unsigned int maxHighPriority, - unsigned int maxStandardPriority, - unsigned int maxLowPriority); - - void SetLocalOrthanc(const std::string& root) - { - oracle_.SetLocalOrthanc(root); - } - - void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) - { - oracle_.SetRemoteOrthanc(orthanc); - } - - void SetDicomCacheSize(size_t size) - { - oracle_.SetDicomCacheSize(size); - } - - virtual ILock* Lock() ORTHANC_OVERRIDE; - }; -}
--- a/OrthancStone/Sources/OpenGL/OpenGLIncludes.h Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Sources/OpenGL/OpenGLIncludes.h Fri Oct 23 13:15:03 2020 +0200 @@ -23,6 +23,14 @@ #include "../OrthancStone.h" +#if !defined(ORTHANC_ENABLE_SDL) +# error The macro ORTHANC_ENABLE_SDL must be defined +#endif + +#if !defined(ORTHANC_ENABLE_WASM) +# error The macro ORTHANC_ENABLE_WASM must be defined +#endif + #if !defined(ORTHANC_ENABLE_OPENGL) # error The macro ORTHANC_ENABLE_OPENGL must be defined #endif @@ -44,73 +52,94 @@ # include <GL/glext.h> #endif -#if ORTHANC_ENABLE_SDL == 1 -# include <SDL_video.h> -# ifdef NDEBUG -// glGetError is very expensive! - -# define ORTHANC_OPENGL_CHECK(name) -# define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) - -# else +#if ORTHANC_ENABLE_SDL == 1 +# include <SDL_video.h> -# define ORTHANC_OPENGL_CHECK(name) \ -if(true) \ -{ \ - GLenum error = glGetError(); \ - if (error != GL_NO_ERROR) { \ - SDL_GLContext ctx = SDL_GL_GetCurrentContext(); \ - LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" << std::hex << ctx << " | error code is " << error; \ - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \ - } \ -} else (void)0 +# if !defined(NDEBUG) // Is build type "debug"? +// glGetError is very expensive! +# include <OrthancException.h> +# define ORTHANC_OPENGL_CHECK(name) \ + if(true) \ + { \ + GLenum error = glGetError(); \ + if (error != GL_NO_ERROR) { \ + SDL_GLContext ctx = SDL_GL_GetCurrentContext(); \ + LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" \ + << std::hex << ctx << " | error code is " << error; \ + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \ + } \ + } else (void)0 -# define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) \ -if(true) \ -{ \ - SDL_GLContext ctx = SDL_GL_GetCurrentContext(); \ - LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \ -} else (void)0 +# define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) \ + if(true) \ + { \ + SDL_GLContext ctx = SDL_GL_GetCurrentContext(); \ + LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \ + } else (void)0 -# endif +# endif /* NDEBUG */ +#endif /* SDL */ -#endif + #if ORTHANC_ENABLE_WASM == 1 -#include <emscripten/html5.h> +# include <emscripten/html5.h> -#define ORTHANC_OPENGL_CHECK(name) \ -if(true) \ -{ \ - GLenum error = glGetError(); \ - if (error != GL_NO_ERROR) { \ - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ - EM_BOOL lost = emscripten_is_webgl_context_lost(ctx); \ - LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" << std::hex << ctx << " | error code is " << error << " | emscripten_is_webgl_context_lost = " << lost; \ - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \ - } \ -} else (void)0 +# if !defined(NDEBUG) // Is build type "debug"? +# include <OrthancException.h> +# define ORTHANC_OPENGL_CHECK(name) \ + if(true) \ + { \ + GLenum error = glGetError(); \ + if (error != GL_NO_ERROR) { \ + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ + EM_BOOL lost = emscripten_is_webgl_context_lost(ctx); \ + LOG(ERROR) << "Error when calling " << name << " | current context is: 0x" \ + << std::hex << ctx << " | error code is " << error << " | emscripten_is_webgl_context_lost = " << lost; \ + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,"OpenGL error in " name " | See log."); \ + } \ + } else (void)0 + +# define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) \ + if(true) \ + { \ + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ + LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \ + } else (void)0 -#define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) \ -if(true) \ -{ \ - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ - LOG(TRACE) << msg << " | Current OpenGL context is " << std::hex << ctx; \ -} else (void)0 +# define ORTHANC_CHECK_CURRENT_CONTEXT(context) \ + if(true) \ + { \ + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ + void* actualCtx = reinterpret_cast<void*>(ctx); \ + void* expectedCtx = context.DebugGetInternalContext(); \ + if(expectedCtx != actualCtx) \ + { \ + LOG(ERROR) << "Expected context was " << std::hex << expectedCtx \ + << " while actual context is " << std::hex << actualCtx; \ + } \ + } else (void)0 -#define ORTHANC_CHECK_CURRENT_CONTEXT(context) \ -if(true) \ -{ \ - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); \ - void* actualCtx = reinterpret_cast<void*>(ctx); \ - void* expectedCtx = context.DebugGetInternalContext(); \ - if(expectedCtx != actualCtx) \ - { \ - LOG(ERROR) << "Expected context was " << std::hex << expectedCtx << " while actual context is " << std::hex << actualCtx; \ - } \ -} else (void)0 +# endif /* NDEBUG */ +#endif /* WASM */ + + + + +// Define void implementation of debug macros if they were not defined above + +#if !defined(ORTHANC_OPENGL_CHECK) +# define ORTHANC_OPENGL_CHECK(name) #endif +#if !defined(ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT) +# define ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT(msg) +#endif + +#if !defined(ORTHANC_CHECK_CURRENT_CONTEXT) +# define ORTHANC_CHECK_CURRENT_CONTEXT(context) +#endif +
--- a/OrthancStone/Sources/OpenGL/SdlOpenGLContext.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "SdlOpenGLContext.h" -#include "../StoneException.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 <GL/glew.h> -#endif - -#include <OrthancException.h> - -namespace OrthancStone -{ - SdlOpenGLContext::SdlOpenGLContext(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) - : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling) - , context_(NULL) - { - 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) - { - LOG(ERROR) << glewGetErrorString(err); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Cannot initialize glew"); - } - - isGlewInitialized_ = true; - } - } -#endif - } - - - SdlOpenGLContext::~SdlOpenGLContext() - { - SDL_GL_DeleteContext(context_); - } - - void SdlOpenGLContext::MakeCurrent() - { - if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0) - { - const char* errText = SDL_GetError(); - std::stringstream ss; - ss << "Cannot set current OpenGL context. SDL error text: " << errText; - std::string errStr = ss.str(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str()); - } - - // This makes our buffer swap synchronized with the monitor's vertical refresh - SDL_GL_SetSwapInterval(1); - } - - - void SdlOpenGLContext::SwapBuffer() - { - // Swap our buffer to display the current contents of buffer on screen - SDL_GL_SwapWindow(window_.GetObject()); - } - - - unsigned int SdlOpenGLContext::GetCanvasWidth() const - { - int w = 0; - SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL); - return static_cast<unsigned int>(w); - } - - - unsigned int SdlOpenGLContext::GetCanvasHeight() const - { - int h = 0; - SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h); - return static_cast<unsigned int>(h); - } -} - -#endif
--- a/OrthancStone/Sources/OpenGL/SdlOpenGLContext.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL == 1 - -#include "IOpenGLContext.h" -#include "../Viewport/SdlWindow.h" - -#include <Compatibility.h> // For ORTHANC_OVERRIDE - -#include <SDL_render.h> - -#include <Enumerations.h> - -namespace OrthancStone -{ - class SdlOpenGLContext : public OpenGL::IOpenGLContext - { - private: - SdlWindow window_; - SDL_GLContext context_; - - public: - SdlOpenGLContext(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - ~SdlOpenGLContext(); - - SdlWindow& GetWindow() - { - return window_; - } - - virtual bool IsContextLost() ORTHANC_OVERRIDE - { - // On desktop applications, an OpenGL context should never be lost - return false; - } - - virtual void MakeCurrent() ORTHANC_OVERRIDE; - - virtual void SwapBuffer() ORTHANC_OVERRIDE; - - unsigned int GetCanvasWidth() const; - - unsigned int GetCanvasHeight() const; - - void ToggleMaximize() - { - window_.ToggleMaximize(); - } - }; -} - -#endif
--- a/OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "WebAssemblyOpenGLContext.h" - -#include "../StoneException.h" - -#include <OrthancException.h> - -#include <emscripten/html5.h> -#include <emscripten/em_asm.h> - -#include <boost/math/special_functions/round.hpp> - -namespace OrthancStone -{ - namespace OpenGL - { - class WebAssemblyOpenGLContext::PImpl - { - private: - std::string canvasSelector_; - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; - bool isContextLost_; - - public: - explicit PImpl(const std::string& canvasSelector) : - canvasSelector_(canvasSelector), - isContextLost_(false) - { - // Context configuration - EmscriptenWebGLContextAttributes attr; - emscripten_webgl_init_context_attributes(&attr); - - // The next line might be necessary to print using - // WebGL. Sometimes, if set to "false" (the default value), - // the canvas was rendered as a fully white or black - // area. UNCONFIRMED. - attr.preserveDrawingBuffer = true; - - context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr); - if (context_ == 0) - { - std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \""); - message += canvasSelector; - message += "\" Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building."; - LOG(ERROR) << message; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message); - } - } - - void* DebugGetInternalContext() const - { - return reinterpret_cast<void*>(context_); - } - - bool IsContextLost() - { - //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec; - bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0); - isContextLost_ = apiFlag; - return isContextLost_; - } - - void SetLostContext() - { - isContextLost_ = true; - } - - ~PImpl() - { - try - { - EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_); - if (result != EMSCRIPTEN_RESULT_SUCCESS) - { - LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result; - } - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What(); - } - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what(); - } - catch (...) - { - LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl"; - } - } - - const std::string& GetCanvasSelector() const - { - return canvasSelector_; - } - - void MakeCurrent() - { - if (IsContextLost()) - { - LOG(ERROR) << "MakeCurrent() called on lost context " << context_; - throw StoneException(ErrorCode_WebGLContextLost); - } - - if (emscripten_is_webgl_context_lost(context_)) - { - LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_; - SetLostContext(); - throw StoneException(ErrorCode_WebGLContextLost); - } - - 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". - **/ - } - }; - - - WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) : - pimpl_(new PImpl(canvasSelector)) - { - } - - bool WebAssemblyOpenGLContext::IsContextLost() - { - return pimpl_->IsContextLost(); - } - - void WebAssemblyOpenGLContext::SetLostContext() - { - pimpl_->SetLostContext(); - } - - void* WebAssemblyOpenGLContext::DebugGetInternalContext() const - { - return pimpl_->DebugGetInternalContext(); - } - - void WebAssemblyOpenGLContext::MakeCurrent() - { - assert(pimpl_.get() != NULL); - pimpl_->MakeCurrent(); - } - - void WebAssemblyOpenGLContext::SwapBuffer() - { - assert(pimpl_.get() != NULL); - pimpl_->SwapBuffer(); - } - - const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const - { - assert(pimpl_.get() != NULL); - return pimpl_->GetCanvasSelector(); - } - } -}
--- a/OrthancStone/Sources/OpenGL/WebAssemblyOpenGLContext.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_WASM) -# error Macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM != 1 -# error This file can only be used if targeting WebAssembly -#endif - -#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 - -#include "IOpenGLContext.h" - -#include <Compatibility.h> // For ORTHANC_OVERRIDE - -#include <boost/shared_ptr.hpp> - -namespace OrthancStone -{ - namespace OpenGL - { - class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext - { - private: - class PImpl; - boost::shared_ptr<PImpl> pimpl_; - - public: - explicit WebAssemblyOpenGLContext(const std::string& canvasSelector); - - virtual bool IsContextLost() ORTHANC_OVERRIDE; - - virtual void MakeCurrent() ORTHANC_OVERRIDE; - - virtual void SwapBuffer() ORTHANC_OVERRIDE; - - /** - Returns true if the underlying context has been successfully recreated - */ - //bool TryRecreate(); - - const std::string& GetCanvasSelector() const; - - - /** - * This is for manual context loss (debug purposes) - **/ - void* DebugGetInternalContext() const; - void SetLostContext(); - }; - } -}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,818 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyOracle_Includes.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include <Oracle/WebAssemblyOracle_Includes.h> -#endif - -#include <OrthancException.h> -#include <Toolbox.h> - -#include <emscripten.h> -#include <emscripten/html5.h> -#include <emscripten/fetch.h> - - -#if ORTHANC_ENABLE_DCMTK == 1 -static unsigned int BUCKET_SOP = 1; -#endif - - -namespace OrthancStone -{ - class WebAssemblyOracle::TimeoutContext - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr<IObserver> receiver_; - std::unique_ptr<SleepOracleCommand> command_; - - public: - TimeoutContext(WebAssemblyOracle& oracle, - boost::weak_ptr<IObserver> receiver, - IOracleCommand* command) : - oracle_(oracle), - receiver_(receiver) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - command_.reset(dynamic_cast<SleepOracleCommand*>(command)); - } - } - - void EmitMessage() - { - assert(command_.get() != NULL); - - SleepOracleCommand::TimeoutMessage message(*command_); - oracle_.EmitMessage(receiver_, message); - } - - static void Callback(void *userData) - { - std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); - context->EmitMessage(); - } - }; - - - /** - This object is created on the heap for every http request. - It is deleted in the success (or error) callbacks. - - This object references the receiver of the request. Since this is a raw - reference, we need additional checks to make sure we send the response to - the same object, for the object can be deleted and a new one recreated at the - same address (it often happens in the [single-threaded] browser context). - */ - class WebAssemblyOracle::FetchContext : public boost::noncopyable - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr<IObserver> receiver_; - std::unique_ptr<IOracleCommand> command_; - std::string expectedContentType_; - - public: - FetchContext(WebAssemblyOracle& oracle, - boost::weak_ptr<IObserver> receiver, - IOracleCommand* command, - const std::string& expectedContentType) : - oracle_(oracle), - receiver_(receiver), - command_(command), - expectedContentType_(expectedContentType) - { - if (Orthanc::Logging::IsTraceLevelEnabled()) - { - // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled - LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | " - << "receiver address = " << std::hex << receiver.lock().get(); - } - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const std::string& GetExpectedContentType() const - { - return expectedContentType_; - } - - IMessageEmitter& GetEmitter() const - { - return oracle_; - } - - boost::weak_ptr<IObserver> GetReceiver() const - { - return receiver_; - } - - void EmitMessage(const IMessage& message) - { - if (Orthanc::Logging::IsTraceLevelEnabled()) - { - // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled - LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = " - << std::hex << receiver_.lock().get() << std::dec; - } - - oracle_.EmitMessage(receiver_, message); - } - - IOracleCommand& GetCommand() const - { - return *command_; - } - - template <typename T> - const T& GetTypedCommand() const - { - return dynamic_cast<T&>(*command_); - } - -#if ORTHANC_ENABLE_DCMTK == 1 - void StoreInCache(const std::string& sopInstanceUid, - std::unique_ptr<Orthanc::ParsedDicomFile>& dicom, - size_t fileSize) - { - if (oracle_.dicomCache_.get()) - { - // Store it into the cache for future use - oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid, - dicom.release(), fileSize, true); - } - } -#endif - - static void SuccessCallback(emscripten_fetch_t *fetch) - { - /** - * Firstly, make a local copy of the fetched information, and - * free data associated with the fetch. - **/ - - if (fetch->userData == NULL) - { - LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!"; - return; - } - - std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); - - std::string answer; - if (fetch->numBytes > 0) - { - answer.assign(fetch->data, fetch->numBytes); - } - - - /** - * Retrieving the headers of the HTTP answer. - **/ - HttpHeaders headers; - -#if (__EMSCRIPTEN_major__ < 1 || \ - (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \ - (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37)) -# warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API - - /** - * HACK - If emscripten < 1.38.37, the fetch API does not - * contain a way to retrieve the HTTP headers of the answer. We - * make the assumption that the "Content-Type" header of the - * response is the same as the "Accept" header of the - * query. This is fixed thanks to the - * "emscripten_fetch_get_response_headers()" function that was - * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26. - * - * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h - * https://github.com/emscripten-core/emscripten/pull/8486 - **/ - if (fetch->userData != NULL) - { - if (!context->GetExpectedContentType().empty()) - { - headers["Content-Type"] = context->GetExpectedContentType(); - } - } -#else - { - size_t size = emscripten_fetch_get_response_headers_length(fetch); - - std::string plainHeaders(size + 1, '\0'); - emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1); - - std::vector<std::string> tokens; - Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n'); - - for (size_t i = 0; i < tokens.size(); i++) - { - size_t p = tokens[i].find(':'); - if (p != std::string::npos) - { - std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p)); - std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1)); - headers[key] = value; - } - } - } -#endif - - LOG(TRACE) << "About to call emscripten_fetch_close"; - emscripten_fetch_close(fetch); - LOG(TRACE) << "Successfully called emscripten_fetch_close"; - - /** - * Secondly, use the retrieved data. - * IMPORTANT NOTE: the receiver might be dead. This is prevented - * by the object responsible for zombie check, later on. - **/ - try - { - if (context.get() == NULL) - { - LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - switch (context->GetCommand().GetType()) - { - case IOracleCommand::Type_Http: - { - HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer); - context->EmitMessage(message); - break; - } - - case IOracleCommand::Type_OrthancRestApi: - { - LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);"; - OrthancRestApiCommand::SuccessMessage message - (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); - context->EmitMessage(message); - break; - } - - case IOracleCommand::Type_GetOrthancImage: - { - context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer - (context->GetReceiver(), context->GetEmitter(), answer, headers); - break; - } - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - { - context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer - (context->GetReceiver(), context->GetEmitter(), answer); - break; - } - - case IOracleCommand::Type_ParseDicomFromWado: - { -#if ORTHANC_ENABLE_DCMTK == 1 - const ParseDicomFromWadoCommand& command = - context->GetTypedCommand<ParseDicomFromWadoCommand>(); - - size_t fileSize; - std::unique_ptr<Orthanc::ParsedDicomFile> dicom - (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers)); - - { - ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true); - context->EmitMessage(message); - } - - context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize); -#else - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); -#endif - break; - } - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): " - << context->GetCommand().GetType(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - } - catch (Orthanc::OrthancException& e) - { - LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What(); - - { - OracleCommandExceptionMessage message(context->GetCommand(), e); - context->EmitMessage(message); - } - } - } - - static void FailureCallback(emscripten_fetch_t *fetch) - { - std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); - -#if 0 - { - const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText); - char message[kEmscriptenStatusTextSize + 1]; - memcpy(message, fetch->statusText, kEmscriptenStatusTextSize); - message[kEmscriptenStatusTextSize] = 0; - - LOG(ERROR) << "Fetching " << fetch->url - << " failed, HTTP failure status code: " << fetch->status - << " | statusText = " << message - << " | numBytes = " << fetch->numBytes - << " | totalBytes = " << fetch->totalBytes - << " | readyState = " << fetch->readyState; - } -#endif - - { - OracleCommandExceptionMessage message - (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol)); - context->EmitMessage(message); - } - - /** - * TODO - The following code leads to an infinite recursion, at - * least with Firefox running on incognito mode => WHY? - **/ - emscripten_fetch_close(fetch); // Also free data on failure. - } - }; - - - - class WebAssemblyOracle::FetchCommand : public boost::noncopyable - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr<IObserver> receiver_; - std::unique_ptr<IOracleCommand> command_; - Orthanc::HttpMethod method_; - std::string url_; - std::string body_; - HttpHeaders headers_; - unsigned int timeout_; - std::string expectedContentType_; - bool hasCredentials_; - std::string username_; - std::string password_; - - public: - FetchCommand(WebAssemblyOracle& oracle, - boost::weak_ptr<IObserver> receiver, - IOracleCommand* command) : - oracle_(oracle), - receiver_(receiver), - command_(command), - method_(Orthanc::HttpMethod_Get), - timeout_(0), - hasCredentials_(false) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - void SetMethod(Orthanc::HttpMethod method) - { - method_ = method; - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - void SetBody(std::string& body /* will be swapped */) - { - body_.swap(body); - } - - void AddHttpHeaders(const HttpHeaders& headers) - { - for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) - { - headers_[it->first] = it->second; - } - } - - void SetTimeout(unsigned int timeout) - { - timeout_ = timeout; - } - - void SetCredentials(const std::string& username, - const std::string& password) - { - hasCredentials_ = true; - username_ = username; - password_ = password; - } - - void Execute() - { - if (command_.get() == NULL) - { - // Cannot call Execute() twice - LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - - const char* method; - - switch (method_) - { - case Orthanc::HttpMethod_Get: - method = "GET"; - break; - - case Orthanc::HttpMethod_Post: - method = "POST"; - break; - - case Orthanc::HttpMethod_Delete: - method = "DELETE"; - break; - - case Orthanc::HttpMethod_Put: - method = "PUT"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - strcpy(attr.requestMethod, method); - - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE; - attr.onsuccess = FetchContext::SuccessCallback; - attr.onerror = FetchContext::FailureCallback; - attr.timeoutMSecs = timeout_ * 1000; - - if (hasCredentials_) - { - attr.withCredentials = EM_TRUE; - attr.userName = username_.c_str(); - attr.password = password_.c_str(); - } - - std::vector<const char*> headers; - headers.reserve(2 * headers_.size() + 1); - - std::string expectedContentType; - - for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) - { - std::string key; - Orthanc::Toolbox::ToLowerCase(key, it->first); - - if (key == "accept") - { - expectedContentType = it->second; - } - - if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header - { - headers.push_back(it->first.c_str()); - headers.push_back(it->second.c_str()); - } - } - - headers.push_back(NULL); // Termination of the array of HTTP headers - - attr.requestHeaders = &headers[0]; - - char* requestData = NULL; - if (!body_.empty()) - requestData = reinterpret_cast<char*>(malloc(body_.size())); - - try - { - if (!body_.empty()) - { - memcpy(requestData, &(body_[0]), body_.size()); - attr.requestDataSize = body_.size(); - attr.requestData = requestData; - } - attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); - - // Must be the last call to prevent memory leak on error - emscripten_fetch(&attr, url_.c_str()); - } - catch(...) - { - if(requestData != NULL) - free(requestData); - throw; - } - } - }; - - - void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command, - const std::string& uri) const - { - if (isLocalOrthanc_) - { - command.SetUrl(localOrthancRoot_ + uri); - } - else - { - command.SetUrl(remoteOrthanc_.GetUrl() + uri); - command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders()); - - if (!remoteOrthanc_.GetUsername().empty()) - { - command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword()); - } - } - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, - HttpCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - fetch.SetMethod(command->GetMethod()); - fetch.SetUrl(command->GetUrl()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - if (command->GetMethod() == Orthanc::HttpMethod_Post || - command->GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body; - command->SwapBody(body); - fetch.SetBody(body); - } - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, - OrthancRestApiCommand* command) - { - try - { - //LOG(TRACE) << "*********** WebAssemblyOracle::Execute."; - //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command; - FetchCommand fetch(*this, receiver, command); - - fetch.SetMethod(command->GetMethod()); - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - if (command->GetMethod() == Orthanc::HttpMethod_Post || - command->GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body; - command->SwapBody(body); - fetch.SetBody(body); - } - - fetch.Execute(); - //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute."; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What(); - } - //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what(); -// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute"; -// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, - GetOrthancImageCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, - GetOrthancWebViewerJpegCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver, - ParseDicomFromWadoCommand* command) - { - std::unique_ptr<ParseDicomFromWadoCommand> protection(command); - -#if ORTHANC_ENABLE_DCMTK == 1 - if (dicomCache_.get()) - { - ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid()); - if (reader.IsValid() && - reader.HasPixelData()) - { - // Reuse the DICOM file from the cache - ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(), - reader.GetFileSize(), reader.HasPixelData()); - EmitMessage(receiver, message); - return; - } - } -#endif - - switch (command->GetRestCommand().GetType()) - { - case IOracleCommand::Type_Http: - { - const HttpCommand& rest = - dynamic_cast<const HttpCommand&>(protection->GetRestCommand()); - - FetchCommand fetch(*this, receiver, protection.release()); - - fetch.SetMethod(rest.GetMethod()); - fetch.SetUrl(rest.GetUrl()); - fetch.AddHttpHeaders(rest.GetHttpHeaders()); - fetch.SetTimeout(rest.GetTimeout()); - - if (rest.GetMethod() == Orthanc::HttpMethod_Post || - rest.GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body = rest.GetBody(); - fetch.SetBody(body); - } - - fetch.Execute(); - break; - } - - case IOracleCommand::Type_OrthancRestApi: - { - const OrthancRestApiCommand& rest = - dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand()); - - FetchCommand fetch(*this, receiver, protection.release()); - - fetch.SetMethod(rest.GetMethod()); - SetOrthancUrl(fetch, rest.GetUri()); - fetch.AddHttpHeaders(rest.GetHttpHeaders()); - fetch.SetTimeout(rest.GetTimeout()); - - if (rest.GetMethod() == Orthanc::HttpMethod_Post || - rest.GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body = rest.GetBody(); - fetch.SetBody(body); - } - - fetch.Execute(); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver, - IOracleCommand* command) - { - LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = " - << std::hex << receiver.get(); - - std::unique_ptr<IOracleCommand> protection(command); - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - switch (command->GetType()) - { - case IOracleCommand::Type_Http: - Execute(receiver, dynamic_cast<HttpCommand*>(protection.release())); - break; - - case IOracleCommand::Type_OrthancRestApi: - Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); - break; - - case IOracleCommand::Type_Sleep: - { - unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); - emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, - new TimeoutContext(*this, receiver, protection.release())); - break; - } - - case IOracleCommand::Type_ParseDicomFromWado: -#if ORTHANC_ENABLE_DCMTK == 1 - Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release())); -#else - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, - "DCMTK must be enabled to parse DICOM files"); -#endif - break; - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): " - << command->GetType(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - return true; - } - - - void WebAssemblyOracle::SetDicomCacheSize(size_t size) - { -#if ORTHANC_ENABLE_DCMTK == 1 - if (size == 0) - { - dicomCache_.reset(); - } - else - { - dicomCache_.reset(new ParsedDicomCache(size)); - } -#else - LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled"; -#endif - } -}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../OrthancStone.h" - -#if !defined(ORTHANC_ENABLE_WASM) -# error The macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM != 1 -# error This file can only compiled for WebAssembly -#endif - -#include "../Messages/IObservable.h" -#include "../Messages/IMessageEmitter.h" -#include "IOracle.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "../Toolbox/ParsedDicomCache.h" -#endif - -#include <Compatibility.h> // For ORTHANC_OVERRIDE -#include <WebServiceParameters.h> - -#include <Enumerations.h> - -namespace OrthancStone -{ - class GetOrthancImageCommand; - class GetOrthancWebViewerJpegCommand; - class HttpCommand; - class OrthancRestApiCommand; - class ParseDicomFromWadoCommand; - - class WebAssemblyOracle : - public IOracle, - public IMessageEmitter - { - private: - typedef std::map<std::string, std::string> HttpHeaders; - - class TimeoutContext; - class FetchContext; - class FetchCommand; - - void SetOrthancUrl(FetchCommand& command, - const std::string& uri) const; - - void Execute(boost::weak_ptr<IObserver> receiver, - HttpCommand* command); - - void Execute(boost::weak_ptr<IObserver> receiver, - OrthancRestApiCommand* command); - - void Execute(boost::weak_ptr<IObserver> receiver, - GetOrthancImageCommand* command); - - void Execute(boost::weak_ptr<IObserver> receiver, - GetOrthancWebViewerJpegCommand* command); - - void Execute(boost::weak_ptr<IObserver> receiver, - ParseDicomFromWadoCommand* command); - - IObservable oracleObservable_; - bool isLocalOrthanc_; - std::string localOrthancRoot_; - Orthanc::WebServiceParameters remoteOrthanc_; - -#if ORTHANC_ENABLE_DCMTK == 1 - std::unique_ptr<ParsedDicomCache> dicomCache_; -#endif - - public: - WebAssemblyOracle() : - isLocalOrthanc_(false) - { - } - - virtual void EmitMessage(boost::weak_ptr<IObserver> observer, - const IMessage& message) ORTHANC_OVERRIDE - { - oracleObservable_.EmitMessage(observer, message); - } - - virtual bool Schedule(boost::shared_ptr<IObserver> receiver, - IOracleCommand* command) ORTHANC_OVERRIDE; - - IObservable& GetOracleObservable() - { - return oracleObservable_; - } - - void SetLocalOrthanc(const std::string& root) - { - isLocalOrthanc_ = true; - localOrthancRoot_ = root; - } - - void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) - { - isLocalOrthanc_ = false; - remoteOrthanc_ = orthanc; - } - - void SetDicomCacheSize(size_t size); - }; -}
--- a/OrthancStone/Sources/Oracle/WebAssemblyOracle_Includes.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -/** - * This file serves as an indirection to avoid large "#if - * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp" - **/ - -#include "WebAssemblyOracle.h" - -#include "OracleCommandExceptionMessage.h" -#include "SleepOracleCommand.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "ParseDicomSuccessMessage.h" -#endif - -#include "GetOrthancImageCommand.h" -#include "GetOrthancWebViewerJpegCommand.h" -#include "HttpCommand.h" -#include "OrthancRestApiCommand.h" -#include "ParseDicomFromWadoCommand.h"
--- a/OrthancStone/Sources/StoneInitialization.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Sources/StoneInitialization.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -21,10 +21,6 @@ #include "StoneInitialization.h" -#if !defined(ORTHANC_ENABLE_SDL) -# error Macro ORTHANC_ENABLE_SDL must be defined -#endif - #if !defined(ORTHANC_ENABLE_SSL) # error Macro ORTHANC_ENABLE_SSL must be defined #endif @@ -40,10 +36,6 @@ # endif #endif -#if ORTHANC_ENABLE_SDL == 1 -# include "Viewport/SdlWindow.h" -#endif - #if ORTHANC_ENABLE_CURL == 1 # include <HttpClient.h> #endif @@ -52,11 +44,6 @@ # include <DicomParsing/FromDcmtkBridge.h> #endif -#if ORTHANC_ENABLE_WASM == 1 -static double viewportsTimeout_ = 1000; -static std::unique_ptr<OrthancStone::WebGLViewportsRegistry> viewportsRegistry_; -#endif - #include "Toolbox/LinearAlgebra.h" #include <Logging.h> @@ -150,23 +137,11 @@ } } } - -#if ORTHANC_ENABLE_SDL == 1 - OrthancStone::SdlWindow::GlobalInitialize(); -#endif } void StoneFinalize() { -#if ORTHANC_ENABLE_WASM == 1 - viewportsRegistry_.reset(); -#endif - -#if ORTHANC_ENABLE_SDL == 1 - OrthancStone::SdlWindow::GlobalFinalize(); -#endif - #if ORTHANC_ENABLE_DCMTK == 1 Orthanc::FromDcmtkBridge::FinalizeCodecs(); #endif @@ -181,32 +156,4 @@ Orthanc::Logging::Finalize(); } - - -#if ORTHANC_ENABLE_WASM == 1 - void SetWebGLViewportsRegistryTimeout(double timeout) - { - if (viewportsRegistry_.get()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - viewportsTimeout_ = timeout; - } - } -#endif - - -#if ORTHANC_ENABLE_WASM == 1 - WebGLViewportsRegistry& GetWebGLViewportsRegistry() - { - if (viewportsRegistry_.get() == NULL) - { - viewportsRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_)); - } - - return *viewportsRegistry_; - } -#endif }
--- a/OrthancStone/Sources/StoneInitialization.h Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Sources/StoneInitialization.h Fri Oct 23 13:15:03 2020 +0200 @@ -21,14 +21,6 @@ #pragma once -#if !defined(ORTHANC_ENABLE_WASM) -# error Macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM == 1 -# include "Viewport/WebGLViewportsRegistry.h" -#endif - #include <Logging.h> namespace OrthancStone @@ -41,12 +33,4 @@ } void StoneFinalize(); - -#if ORTHANC_ENABLE_WASM == 1 - void SetWebGLViewportsRegistryTimeout(double timeout); -#endif - -#if ORTHANC_ENABLE_WASM == 1 - WebGLViewportsRegistry& GetWebGLViewportsRegistry(); -#endif }
--- a/OrthancStone/Sources/Toolbox/GenericToolbox.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Sources/Toolbox/GenericToolbox.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -21,6 +21,8 @@ #include "GenericToolbox.h" +#include <Toolbox.h> + #include <boost/regex.hpp> namespace OrthancStone @@ -100,5 +102,12 @@ } } + + void NormalizeUuid(std::string& uuid) + { + std::string temp = Orthanc::Toolbox::StripSpaces(uuid); + Orthanc::Toolbox::ToLowerCase(temp); + uuid.swap(temp); + } } }
--- a/OrthancStone/Sources/Toolbox/GenericToolbox.h Thu Oct 22 18:39:03 2020 +0200 +++ b/OrthancStone/Sources/Toolbox/GenericToolbox.h Fri Oct 23 13:15:03 2020 +0200 @@ -304,5 +304,12 @@ { return GetRgbaValuesFromString(red, green, blue, alpha, text.c_str()); } + + + /** + This method could have been called StripSpacesAndChangeToLower but we might want to + add some UUID validation to the argument + */ + void NormalizeUuid(std::string& uuid); } }
--- a/OrthancStone/Sources/Viewport/SdlViewport.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,236 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - -#include "SdlViewport.h" - -#include "../Scene2DViewport/ViewportController.h" - -#include <OrthancException.h> - -#include <boost/make_shared.hpp> - -namespace OrthancStone -{ - ICompositor& SdlViewport::SdlLock::GetCompositor() - { - if (that_.compositor_.get() == NULL) - { - // The derived class should have called "AcquireCompositor()" - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return *that_.compositor_; - } - } - - - void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) - { - if (compositor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - compositor_.reset(compositor); - } - - - SdlViewport::SdlViewport() - { - refreshEvent_ = SDL_RegisterEvents(1); - - if (refreshEvent_ == static_cast<uint32_t>(-1)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void SdlViewport::PostConstructor() - { - controller_ = boost::make_shared<ViewportController>(shared_from_this()); - } - - void SdlViewport::SendRefreshEvent() - { - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = refreshEvent_; - SDL_PushEvent(&event); // This function is thread-safe, and can be called from other threads safely. - } - - - void SdlViewport::UpdateSize(unsigned int width, unsigned int height) - { - SdlLock lock(*this); - lock.GetCompositor().SetCanvasSize(width, height); - lock.Invalidate(); - } - - - SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) : - context_(title.c_str(), width, height, allowDpiScaling) - { - AcquireCompositor(new OpenGLCompositor(context_)); // (*) - } - - - void SdlOpenGLViewport::RefreshCanvasSize() - { - UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight()); - } - - - boost::shared_ptr<SdlOpenGLViewport> SdlOpenGLViewport::Create( - const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) - { - boost::shared_ptr<SdlOpenGLViewport> that = - boost::shared_ptr<SdlOpenGLViewport>(new SdlOpenGLViewport(title, width, height, allowDpiScaling)); - that->SdlViewport::PostConstructor(); - return that; - } - - uint32_t SdlOpenGLViewport::GetSdlWindowId() - { - const SdlWindow& sdlWindowWrapper = context_.GetWindow(); - SDL_Window* sdlWindow = sdlWindowWrapper.GetObject(); - Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); - return sdlWindowId; - } - - SdlOpenGLViewport::~SdlOpenGLViewport() - { - // Make sure that the "OpenGLCompositor" is destroyed BEFORE the - // "OpenGLContext" it references (*) - ClearCompositor(); - } - - - void SdlOpenGLViewport::Paint() - { - SdlLock lock(*this); - lock.GetCompositor().Refresh(lock.GetController().GetScene()); - } - - - void SdlOpenGLViewport::ToggleMaximize() - { - // No need to call "Invalidate()" here, as "UpdateSize()" will - // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" - SdlLock lock(*this); - context_.ToggleMaximize(); - } - - - - void SdlCairoViewport::RefreshCanvasSize() - { - UpdateSize(window_.GetWidth(), window_.GetHeight()); - } - - SdlCairoViewport::SdlCairoViewport(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) : - window_(title, width, height, false /* enable OpenGL */, allowDpiScaling), - sdlSurface_(NULL) - { - AcquireCompositor(new CairoCompositor(width, height)); - } - - SdlCairoViewport::~SdlCairoViewport() - { - if (sdlSurface_) - { - SDL_FreeSurface(sdlSurface_); - } - } - - uint32_t SdlCairoViewport::GetSdlWindowId() - { - SDL_Window* sdlWindow = window_.GetObject(); - Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); - return sdlWindowId; - } - - void SdlCairoViewport::Paint() - { - SdlLock lock(*this); - - lock.GetCompositor().Refresh(lock.GetController().GetScene()); - CreateSdlSurfaceFromCompositor(dynamic_cast<CairoCompositor&>(lock.GetCompositor())); - - if (sdlSurface_ != NULL) - { - window_.Render(sdlSurface_); - } - } - - - void SdlCairoViewport::ToggleMaximize() - { - // No need to call "Invalidate()" here, as "UpdateSize()" will - // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" - SdlLock lock(*this); - window_.ToggleMaximize(); - } - - - // Assumes that the mutex is locked - void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor) - { - static const uint32_t rmask = 0x00ff0000; - static const uint32_t gmask = 0x0000ff00; - static const uint32_t bmask = 0x000000ff; - - const unsigned int width = compositor.GetCanvas().GetWidth(); - const unsigned int height = compositor.GetCanvas().GetHeight(); - - if (sdlSurface_ != NULL) - { - if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() && - sdlSurface_->w == static_cast<int>(width) && - sdlSurface_->h == static_cast<int>(height) && - sdlSurface_->pitch == static_cast<int>(compositor.GetCanvas().GetPitch())) - { - // The image from the compositor has not changed, no need to update the surface - return; - } - else - { - SDL_FreeSurface(sdlSurface_); - } - } - - sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32, - compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); - if (!sdlSurface_) - { - LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } -}
--- a/OrthancStone/Sources/Viewport/SdlViewport.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - -#pragma once - -#if !defined(ORTHANC_ENABLE_SDL) -# error Macro ORTHANC_ENABLE_SDL must be defined -#endif - -#if ORTHANC_ENABLE_SDL != 1 -# error SDL must be enabled to use this file -#endif - -#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 - -#include "../OpenGL/SdlOpenGLContext.h" -#include "../Scene2D/OpenGLCompositor.h" -#include "../Scene2D/CairoCompositor.h" -#include "IViewport.h" - -#include <SDL_events.h> - -// TODO: required for UndoStack injection -// I don't like it either :) -#include <boost/weak_ptr.hpp> - -#include <boost/thread/recursive_mutex.hpp> -#include <boost/enable_shared_from_this.hpp> - -namespace OrthancStone -{ - class UndoStack; - - class SdlViewport : public IViewport, - public boost::enable_shared_from_this<SdlViewport> - { - private: - boost::recursive_mutex mutex_; - uint32_t refreshEvent_; - boost::shared_ptr<ViewportController> controller_; - std::unique_ptr<ICompositor> compositor_; - - void SendRefreshEvent(); - - protected: - class SdlLock : public ILock - { - private: - SdlViewport& that_; - boost::recursive_mutex::scoped_lock lock_; - - public: - explicit SdlLock(SdlViewport& that) : - that_(that), - lock_(that.mutex_) - { - } - - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return true; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; - - virtual ViewportController& GetController() ORTHANC_OVERRIDE - { - return *that_.controller_; - } - - virtual void Invalidate() ORTHANC_OVERRIDE - { - that_.SendRefreshEvent(); - } - - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE - { - that_.RefreshCanvasSize(); - } - }; - - void ClearCompositor() - { - compositor_.reset(); - } - - void AcquireCompositor(ICompositor* compositor /* takes ownership */); - - virtual void RefreshCanvasSize() = 0; - - protected: - SdlViewport(); - - void PostConstructor(); - - public: - bool IsRefreshEvent(const SDL_Event& event) const - { - return (event.type == refreshEvent_); - } - - virtual ILock* Lock() ORTHANC_OVERRIDE - { - return new SdlLock(*this); - } - - virtual uint32_t GetSdlWindowId() = 0; - - void UpdateSize(unsigned int width, - unsigned int height); - - virtual void ToggleMaximize() = 0; - - // Must be invoked from the main SDL thread - virtual void Paint() = 0; - }; - - - class SdlOpenGLViewport : public SdlViewport - { - private: - SdlOpenGLContext context_; - - SdlOpenGLViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - protected: - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr<SdlOpenGLViewport> Create(const std::string&, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - - virtual ~SdlOpenGLViewport(); - - virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; - - virtual void Paint() ORTHANC_OVERRIDE; - - virtual void ToggleMaximize() ORTHANC_OVERRIDE; - }; - - - class SdlCairoViewport : public SdlViewport - { - private: - SdlWindow window_; - SDL_Surface* sdlSurface_; - - void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor); - - SdlCairoViewport(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - protected: - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr<SdlCairoViewport> Create(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - - virtual ~SdlCairoViewport(); - - virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; - - virtual void Paint() ORTHANC_OVERRIDE; - - virtual void ToggleMaximize() ORTHANC_OVERRIDE; - }; -}
--- a/OrthancStone/Sources/Viewport/SdlWindow.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "SdlWindow.h" - -#if ORTHANC_ENABLE_SDL == 1 - -#include <Logging.h> -#include <OrthancException.h> - -#ifdef WIN32 -#include <windows.h> // for SetProcessDpiAware -#endif -// WIN32 - -#include <SDL_render.h> -#include <SDL_video.h> -#include <SDL.h> - -namespace OrthancStone -{ - SdlWindow::SdlWindow(const char* title, - unsigned int width, - unsigned int height, - bool enableOpenGl, - bool allowDpiScaling) : - maximized_(false) - { - // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release - // build mode, the application crashes whenever the SDL window is - // resized or maximized - - uint32_t windowFlags, rendererFlags; - if (enableOpenGl) - { - windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; - rendererFlags = SDL_RENDERER_ACCELERATED; - } - else - { - 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 <windows.h> - //#include <ShellScalingAPI.h> THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness - //#include <comdef.h> 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, - SDL_WINDOWPOS_UNDEFINED, - width, height, windowFlags); - - if (window_ == NULL) - { - LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags); - if (!renderer_) - { - LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError(); - SDL_DestroyWindow(window_); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - SdlWindow::~SdlWindow() - { - if (renderer_ != NULL) - { - SDL_DestroyRenderer(renderer_); - } - - if (window_ != NULL) - { - SDL_DestroyWindow(window_); - } - } - - - unsigned int SdlWindow::GetWidth() const - { - int w = -1; - SDL_GetWindowSize(window_, &w, NULL); - - if (w < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return static_cast<unsigned int>(w); - } - } - - - unsigned int SdlWindow::GetHeight() const - { - int h = -1; - SDL_GetWindowSize(window_, NULL, &h); - - if (h < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return static_cast<unsigned int>(h); - } - } - - - void SdlWindow::Render(SDL_Surface* surface) - { - /** - * "You are strongly encouraged to call SDL_RenderClear() to - * initialize the backbuffer before starting each new frame's - * drawing, even if you plan to overwrite every pixel." - * https://wiki.libsdl.org/SDL_RenderPresent - **/ - SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); - SDL_RenderClear(renderer_); // Clear the entire screen to our selected color - - SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface); - if (texture != NULL) - { - SDL_RenderCopy(renderer_, texture, NULL, NULL); - SDL_DestroyTexture(texture); - } - - SDL_RenderPresent(renderer_); - } - - - void SdlWindow::ToggleMaximize() - { - if (maximized_) - { - SDL_RestoreWindow(window_); - maximized_ = false; - } - else - { - SDL_MaximizeWindow(window_); - maximized_ = true; - } - } - - - void SdlWindow::GlobalInitialize() - { - if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - LOG(ERROR) << "Cannot initialize SDL"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - void SdlWindow::GlobalFinalize() - { - SDL_Quit(); - } -} - -#endif
--- a/OrthancStone/Sources/Viewport/SdlWindow.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL == 1 - -#include <boost/noncopyable.hpp> - -// Forward declaration of SDL type to avoid clashes with DCMTK headers -// on "typedef Sint8", in "StoneInitialization.cpp" -struct SDL_Window; -struct SDL_Renderer; -struct SDL_Surface; - -namespace OrthancStone -{ - class SdlWindow : public boost::noncopyable - { - private: - struct SDL_Window *window_; - struct SDL_Renderer *renderer_; - bool maximized_; - - public: - SdlWindow(const char* title, - unsigned int width, - unsigned int height, - bool enableOpenGl, - bool allowDpiScaling = true); - - ~SdlWindow(); - - SDL_Window *GetObject() const - { - return window_; - } - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - /** - * WARNING: "Refresh()" cannot only be called from the main SDL - * thread, in which the window was created. Otherwise, the - * renderer displays nothing! - **/ - void Render(struct SDL_Surface* surface); - - void ToggleMaximize(); - - static void GlobalInitialize(); - - static void GlobalFinalize(); - }; -} - -#endif
--- a/OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyCairoViewport.h" -# include "../Scene2D/CairoCompositor.h" -# include "../Scene2DViewport/ViewportController.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include <Viewport/WebAssemblyCairoViewport.h> -# include <Scene2D/CairoCompositor.h> -# include <Scene2DViewport/ViewportController.h> -#endif - - -#include <Images/Image.h> - -namespace OrthancStone -{ - void WebAssemblyCairoViewport::Paint(ICompositor& compositor, - ViewportController& controller) - { - compositor.Refresh(controller.GetScene()); - - // Create a temporary memory buffer for the canvas in JavaScript - Orthanc::ImageAccessor cairo; - dynamic_cast<CairoCompositor&>(compositor).GetCanvas().GetReadOnlyAccessor(cairo); - - const unsigned int width = cairo.GetWidth(); - const unsigned int height = cairo.GetHeight(); - - if (javascript_.get() == NULL || - javascript_->GetWidth() != width || - javascript_->GetHeight() != height) - { - javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, - true /* force minimal pitch */)); - } - - // Convert from BGRA32 memory layout (only color mode supported - // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 - // (as expected by HTML5 canvas). This simply amounts to - // swapping the B and R channels. Alpha channel is also set to - // full opacity (255). - uint8_t* q = reinterpret_cast<uint8_t*>(javascript_->GetBuffer()); - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast<const uint8_t*>(cairo.GetConstRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[2]; // R - q[1] = p[1]; // G - q[2] = p[0]; // B - q[3] = 255; // A - - p += 4; - q += 4; - } - } - - // Execute JavaScript commands to blit the image buffer onto the - // 2D drawing context of the HTML5 canvas - EM_ASM({ - const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3); - const img = new ImageData(data, $2, $3); - const ctx = document.getElementById(UTF8ToString($0)).getContext('2d'); - ctx.putImageData(img, 0, 0); - }, - GetCanvasId().c_str(), // $0 - javascript_->GetBuffer(), // $1 - javascript_->GetWidth(), // $2 - javascript_->GetHeight()); // $3 - } - - - WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents) : - WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents) - { - RefreshCanvasSize(); - AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight())); - } - - - boost::shared_ptr<WebAssemblyCairoViewport> WebAssemblyCairoViewport::Create( - const std::string& canvasId, bool enableEmscriptenMouseEvents) - { - boost::shared_ptr<WebAssemblyCairoViewport> that = boost::shared_ptr<WebAssemblyCairoViewport>( - new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents)); - - that->WebAssemblyViewport::PostConstructor(); - return that; - } -}
--- a/OrthancStone/Sources/Viewport/WebAssemblyCairoViewport.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "WebAssemblyViewport.h" - -namespace OrthancStone -{ - class WebAssemblyCairoViewport : public WebAssemblyViewport - { - private: - std::unique_ptr<Orthanc::ImageAccessor> javascript_; - - WebAssemblyCairoViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - protected: - virtual void Paint(ICompositor& compositor, - ViewportController& controller) ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr<WebAssemblyCairoViewport> Create(const std::string& canvasId, - bool enableEmscriptenMouseEvents = true); - - virtual ~WebAssemblyCairoViewport() - { - ClearCompositor(); - } - }; -}
--- a/OrthancStone/Sources/Viewport/WebAssemblyViewport.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,399 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyViewport.h" -# include "DefaultViewportInteractor.h" -# include "../Toolbox/GenericToolbox.h" -# include "../Scene2DViewport/ViewportController.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include <Viewport/WebAssemblyViewport.h> -# include <Toolbox/GenericToolbox.h> -# include <Scene2DViewport/ViewportController.h> -# include <Viewport/DefaultViewportInteractor.h> -#endif - - -#include <OrthancException.h> - -#include <boost/make_shared.hpp> -#include <boost/enable_shared_from_this.hpp> -#include <boost/math/special_functions/round.hpp> - -namespace OrthancStone -{ - static void ConvertMouseEvent(PointerEvent& target, - const EmscriptenMouseEvent& source, - const ICompositor& compositor) - { - int x = static_cast<int>(source.targetX); - int y = static_cast<int>(source.targetY); - - switch (source.button) - { - case 0: - target.SetMouseButton(MouseButton_Left); - break; - - case 1: - target.SetMouseButton(MouseButton_Middle); - break; - - case 2: - target.SetMouseButton(MouseButton_Right); - break; - - default: - target.SetMouseButton(MouseButton_None); - break; - } - - target.AddPosition(compositor.GetPixelCenterCoordinates(x, y)); - target.SetAltModifier(source.altKey); - target.SetControlModifier(source.ctrlKey); - target.SetShiftModifier(source.shiftKey); - } - - - class WebAssemblyViewport::WasmLock : public ILock - { - private: - WebAssemblyViewport& that_; - - public: - WasmLock(WebAssemblyViewport& that) : - that_(that) - { - } - - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return that_.compositor_.get() != NULL; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE - { - if (that_.compositor_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *that_.compositor_; - } - } - - virtual ViewportController& GetController() ORTHANC_OVERRIDE - { - assert(that_.controller_); - return *that_.controller_; - } - - virtual void Invalidate() ORTHANC_OVERRIDE - { - that_.Invalidate(); - } - - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE - { - that_.RefreshCanvasSize(); - } - }; - - - EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData) - { - LOG(TRACE) << __func__; - WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); - - if (that->compositor_.get() != NULL && - that->controller_ /* should always be true */) - { - that->Paint(*that->compositor_, *that->controller_); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) - { - LOG(TRACE) << __func__; - WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); - - if (that->compositor_.get() != NULL) - { - that->RefreshCanvasSize(); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - - EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); - - LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector(); - - if (that->compositor_.get() != NULL && - that->interactor_.get() != NULL) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - - that->controller_->HandleMousePress(*that->interactor_, pointer, - that->compositor_->GetCanvasWidth(), - that->compositor_->GetCanvasHeight()); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - - EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); - - if (that->compositor_.get() != NULL && - that->controller_->HasActiveTracker()) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - if (that->controller_->HandleMouseMove(pointer)) - { - that->Invalidate(); - } - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - LOG(TRACE) << __func__; - WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData); - - if (that->compositor_.get() != NULL) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - that->controller_->HandleMouseRelease(pointer); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - void WebAssemblyViewport::Invalidate() - { - emscripten_request_animation_frame(OnRequestAnimationFrame, reinterpret_cast<void*>(this)); - } - - void WebAssemblyViewport::FitForPrint() - { - if (compositor_.get() != NULL && - controller_ /* should always be true */) - { - RefreshCanvasSize(); - compositor_->FitContent(controller_->GetScene()); - OnRequestAnimationFrame(0, reinterpret_cast<void*>(this)); // Mandatory to work with Firefox - } - } - - void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) - { - if (compositor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - compositor_.reset(compositor); - } - } - -#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 -// everything OK..... we're using the new setting -#else -#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.") -#endif - - WebAssemblyViewport::WebAssemblyViewport( - const std::string& canvasId, bool enableEmscriptenMouseEvents) : - canvasId_(canvasId), -#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 - canvasCssSelector_("#" + canvasId), -#else - canvasCssSelector_(canvasId), -#endif - interactor_(new DefaultViewportInteractor), - enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents), - canvasWidth_(0), - canvasHeight_(0) - { - } - - void WebAssemblyViewport::PostConstructor() - { - boost::shared_ptr<IViewport> viewport = shared_from_this(); - controller_.reset(new ViewportController(viewport)); - - LOG(INFO) << "Initializing Stone viewport on HTML canvas: " - << canvasId_; - - if (canvasId_.empty() || - canvasId_[0] == '#') - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "The canvas identifier must not start with '#'"); - } - - // Disable right-click on the canvas (i.e. context menu) - EM_ASM({ - document.getElementById(UTF8ToString($0)).oncontextmenu = - function(event) - { - event.preventDefault(); - } - }, - canvasId_.c_str() // $0 - ); - - // It is not possible to monitor the resizing of individual - // canvas, so we track the full window of the browser - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, - reinterpret_cast<void*>(this), - false, - OnResize); - - if (enableEmscriptenMouseEvents_) - { - - // if any of this function causes an error in the console, please - // make sure you are using the new (as of 1.39.x) version of - // emscripten element lookup rules( pass - // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker. - - emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - OnMouseDown); - - emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - OnMouseMove); - - emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - OnMouseUp); - } - } - - WebAssemblyViewport::~WebAssemblyViewport() - { - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, - reinterpret_cast<void*>(this), - false, - NULL); - - if (enableEmscriptenMouseEvents_) - { - - emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - NULL); - - emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - NULL); - - emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), - reinterpret_cast<void*>(this), - false, - NULL); - } - } - - IViewport::ILock* WebAssemblyViewport::Lock() - { - return new WasmLock(*this); - } - - void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor) - { - if (interactor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - interactor_.reset(interactor); - } - } - - - void WebAssemblyViewport::RefreshCanvasSize() - { - double w, h; - emscripten_get_element_css_size(GetCanvasCssSelector().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_ = static_cast<unsigned int>(boost::math::iround(w)); - canvasHeight_ = static_cast<unsigned int>(boost::math::iround(h)); - } - else - { - canvasWidth_ = 0; - canvasHeight_ = 0; - } - - emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_); - - if (compositor_.get() != NULL) - { - compositor_->SetCanvasSize(canvasWidth_, canvasHeight_); - } - } -}
--- a/OrthancStone/Sources/Viewport/WebAssemblyViewport.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../OrthancStone.h" - -#if !defined(ORTHANC_ENABLE_WASM) -# error Macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM != 1 -# error This file can only be used if targeting WebAssembly -#endif - -#include "IViewport.h" -#include "IViewportInteractor.h" - -#include <Compatibility.h> - -#include <emscripten.h> -#include <emscripten/html5.h> - -#include <memory> -#include <string> -#include <boost/enable_shared_from_this.hpp> - -namespace OrthancStone -{ - class WebAssemblyViewport : public IViewport, - public boost::enable_shared_from_this<WebAssemblyViewport> - - { - private: - class WasmLock; - - std::string canvasId_; - std::string canvasCssSelector_; - std::unique_ptr<ICompositor> compositor_; - std::unique_ptr<ViewportController> controller_; - std::unique_ptr<IViewportInteractor> interactor_; - bool enableEmscriptenMouseEvents_; - unsigned int canvasWidth_; - unsigned int canvasHeight_; - - static EM_BOOL OnRequestAnimationFrame(double time, void *userData); - - static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); - - static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - protected: - void Invalidate(); - - void ClearCompositor() - { - compositor_.reset(); - } - - bool HasCompositor() const - { - return compositor_.get() != NULL; - } - - void AcquireCompositor(ICompositor* compositor /* takes ownership */); - - virtual void Paint(ICompositor& compositor, - ViewportController& controller) = 0; - - /** - The second argument is temporary and should be deleted once the migration - to interactors is finished. It should be set to "true" for new applications. - */ - WebAssemblyViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - void PostConstructor(); - - void RefreshCanvasSize(); - - unsigned int GetCanvasWidth() const - { - return canvasWidth_; - } - - unsigned int GetCanvasHeight() - { - return canvasHeight_; - } - - public: - virtual ILock* Lock() ORTHANC_OVERRIDE; - - ~WebAssemblyViewport(); - - /** - This method takes ownership - */ - void AcquireInteractor(IViewportInteractor* interactor); - - const std::string& GetCanvasId() const - { - return canvasId_; - } - - /** - emscripten functions requires the css selector for the canvas. This is - different from the canvas id (the syntax is '#mycanvasid') - */ - const std::string& GetCanvasCssSelector() const - { - return canvasCssSelector_; - } - - void FitForPrint(); - }; -}
--- a/OrthancStone/Sources/Viewport/WebGLViewport.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "WebGLViewport.h" - -#include "../StoneException.h" -#include "../Scene2D/OpenGLCompositor.h" -#include "../Scene2DViewport/ViewportController.h" - -namespace OrthancStone -{ - void WebGLViewport::Paint(ICompositor& compositor, - ViewportController& controller) - { - try - { - compositor.Refresh(controller.GetScene()); - - /** - * No need to manually swap the buffer: "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". - **/ - } - catch (const StoneException& e) - { - // Ignore problems about the loss of the WebGL context (edge case) - if (e.GetErrorCode() == ErrorCode_WebGLContextLost) - { - return; - } - else - { - throw; - } - } - } - - - WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) : - WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents), - context_(GetCanvasCssSelector()) - { - AcquireCompositor(new OpenGLCompositor(context_)); - } - - boost::shared_ptr<WebGLViewport> WebGLViewport::Create( - const std::string& canvasId, bool enableEmscriptenMouseEvents) - { - boost::shared_ptr<WebGLViewport> that = boost::shared_ptr<WebGLViewport>( - new WebGLViewport(canvasId, enableEmscriptenMouseEvents)); - - that->WebAssemblyViewport::PostConstructor(); - return that; - } - - WebGLViewport::~WebGLViewport() - { - // Make sure to delete the compositor before its parent "context_" gets - // deleted - ClearCompositor(); - } -}
--- a/OrthancStone/Sources/Viewport/WebGLViewport.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "WebAssemblyViewport.h" -#include "../OpenGL/WebAssemblyOpenGLContext.h" - -namespace OrthancStone -{ - class WebGLViewport : public WebAssemblyViewport - { - private: - OpenGL::WebAssemblyOpenGLContext context_; - - WebGLViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - protected: - virtual void Paint(ICompositor& compositor, - ViewportController& controller) ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr<WebGLViewport> Create(const std::string& canvasId, - bool enableEmscriptenMouseEvents = true); - - virtual ~WebGLViewport(); - - bool IsContextLost() - { - return context_.IsContextLost(); - } - }; -}
--- a/OrthancStone/Sources/Viewport/WebGLViewportsRegistry.cpp Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "WebGLViewportsRegistry.h" - -#include "../Toolbox/GenericToolbox.h" -#include "../Scene2DViewport/ViewportController.h" - -#include <OrthancException.h> - -#include <boost/make_shared.hpp> - -namespace OrthancStone -{ - void WebGLViewportsRegistry::LaunchTimer() - { - timeOutID_ = emscripten_set_timeout( - OnTimeoutCallback, - timeoutMS_, - reinterpret_cast<void*>(this)); - } - - void WebGLViewportsRegistry::OnTimeout() - { - for (Viewports::iterator it = viewports_.begin(); - it != viewports_.end(); - ++it) - { - if (it->second == NULL || - it->second->IsContextLost()) - { - LOG(INFO) << "WebGL context lost for canvas: " << it->first; - - // Try and duplicate the HTML5 canvas in the DOM - EM_ASM({ - var canvas = document.getElementById(UTF8ToString($0)); - if (canvas) { - var parent = canvas.parentElement; - if (parent) { - var cloned = canvas.cloneNode(true /* deep copy */); - parent.insertBefore(cloned, canvas); - parent.removeChild(canvas); - } - } - }, - it->first.c_str() // $0 = ID of the canvas - ); - - // At this point, the old canvas is removed from the DOM and - // replaced by a fresh one with the same ID: Recreate the - // WebGL context on the new canvas - boost::shared_ptr<WebGLViewport> viewport; - - // we need to steal the properties from the old viewport - // and set them to the new viewport - { - std::unique_ptr<IViewport::ILock> lock(it->second->Lock()); - - // TODO: remove ViewportController - Scene2D* scene = lock->GetController().ReleaseScene(); - viewport = WebGLViewport::Create(it->first); - - { - std::unique_ptr<IViewport::ILock> newLock(viewport->Lock()); - newLock->GetController().AcquireScene(scene); - } - } - - // Replace the old WebGL viewport by the new one - it->second = viewport; - - // Tag the fresh canvas as needing a repaint - { - std::unique_ptr<IViewport::ILock> lock(it->second->Lock()); - lock->Invalidate(); - } - } - } - - LaunchTimer(); - } - - void WebGLViewportsRegistry::OnTimeoutCallback(void *userData) - { - // This object dies with the process or tab. - WebGLViewportsRegistry* that = - reinterpret_cast<WebGLViewportsRegistry*>(userData); - that->OnTimeout(); - } - - WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) : - timeoutMS_(timeoutMS), - timeOutID_(0) - { - if (timeoutMS <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - LaunchTimer(); - } - - WebGLViewportsRegistry::~WebGLViewportsRegistry() - { - emscripten_clear_timeout(timeOutID_); - Clear(); - } - - boost::shared_ptr<WebGLViewport> WebGLViewportsRegistry::Add( - const std::string& canvasId) - { - if (viewports_.find(canvasId) != viewports_.end()) - { - LOG(ERROR) << "Canvas was already registered: " << canvasId; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - boost::shared_ptr<WebGLViewport> viewport = - WebGLViewport::Create(canvasId); - viewports_[canvasId] = viewport; - return viewport; - } - } - - void WebGLViewportsRegistry::Remove(const std::string& canvasId) - { - Viewports::iterator found = viewports_.find(canvasId); - - if (found == viewports_.end()) - { - LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId; - } - else - { - viewports_.erase(found); - } - } - - void WebGLViewportsRegistry::Clear() - { - viewports_.clear(); - } - - WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that, - const std::string& canvasId) : - that_(that) - { - Viewports::iterator viewport = that.viewports_.find(canvasId); - if (viewport != that.viewports_.end() && - viewport->second != NULL) - { - lock_.reset(viewport->second->Lock()); - } - } - - IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const - { - if (IsValid()) - { - return *lock_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } -}
--- a/OrthancStone/Sources/Viewport/WebGLViewportsRegistry.h Thu Oct 22 18:39:03 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "WebGLViewport.h" - -#include <boost/enable_shared_from_this.hpp> - -namespace OrthancStone -{ - /** - * This singleton class must be used if many WebGL viewports are - * created by the higher-level application, implying possible loss - * of WebGL contexts. The object will run an infinite update loop - * that checks whether all the WebGL context are still valid (not - * lost). If some WebGL context is lost, it is automatically - * reinitialized by created a fresh HTML5 canvas. - **/ - class WebGLViewportsRegistry : public boost::noncopyable, - public boost::enable_shared_from_this<WebGLViewportsRegistry> - { - private: - typedef std::map<std::string, boost::shared_ptr<WebGLViewport> > Viewports; - - double timeoutMS_; - Viewports viewports_; - long timeOutID_; - - void LaunchTimer(); - - void OnTimeout(); - - static void OnTimeoutCallback(void *userData); - - public: - explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */); - - ~WebGLViewportsRegistry(); - - boost::shared_ptr<WebGLViewport> Add(const std::string& canvasId); - - void Remove(const std::string& canvasId); - - void Clear(); - - class Accessor : public boost::noncopyable - { - private: - WebGLViewportsRegistry& that_; - std::unique_ptr<IViewport::ILock> lock_; - - public: - Accessor(WebGLViewportsRegistry& that, - const std::string& canvasId); - - bool IsValid() const - { - return lock_.get() != NULL; - } - - IViewport::ILock& GetViewport() const; - }; - }; -}
--- a/UnitTestsSources/UnitTestsMain.cpp Thu Oct 22 18:39:03 2020 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Oct 23 13:15:03 2020 +0200 @@ -23,9 +23,9 @@ #include "../OrthancStone/Sources/StoneInitialization.h" #include "../OrthancStone/Sources/Toolbox/FiniteProjectiveCamera.h" +#include "../OrthancStone/Sources/Toolbox/GenericToolbox.h" #include "../OrthancStone/Sources/Toolbox/GeometryToolbox.h" #include "../OrthancStone/Sources/Volumes/ImageBuffer3D.h" -#include "../OrthancStone/Sources/Loaders/LoaderCache.h" #include <Images/ImageProcessing.h> #include <Logging.h> @@ -811,69 +811,69 @@ ASSERT_DOUBLE_EQ(1.3671875, v[1]); } -TEST(LoaderCache, NormalizeUuid) +TEST(GenericToolbox, NormalizeUuid) { std::string ref("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb"); { std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb"); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space left { std::string u(" 44ca5051-14ef-4d2f-8bd7-db20bfb61fbb"); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space right { std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fbb "); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space l & r { std::string u(" 44ca5051-14ef-4d2f-8bd7-db20bfb61fbb "); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space left + case { std::string u(" 44CA5051-14ef-4d2f-8bd7-dB20bfb61fbb"); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space right + case { std::string u("44ca5051-14EF-4D2f-8bd7-db20bfb61fbB "); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // space l & r + case { std::string u(" 44cA5051-14Ef-4d2f-8bD7-db20bfb61fbb "); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_EQ(ref, u); } // no { std::string u(" 44ca5051-14ef-4d2f-8bd7- db20bfb61fbb"); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_NE(ref, u); } // no { std::string u("44ca5051-14ef-4d2f-8bd7-db20bfb61fb"); - OrthancStone::LoaderCache::NormalizeUuid(u); + OrthancStone::GenericToolbox::NormalizeUuid(u); ASSERT_NE(ref, u); } }