Mercurial > hg > orthanc-stone
changeset 236:f73d722d98c8 am
renamed folder
author | am@osimis.io |
---|---|
date | Tue, 19 Jun 2018 16:06:32 +0200 |
parents | ce4405d98b92 |
children | b4642964c355 |
files | Platforms/Wasm/CMakeLists.txt Platforms/Wasm/Defaults.cpp Platforms/Wasm/Defaults.h Platforms/Wasm/WasmViewport.h Platforms/Wasm/WasmWebService.cpp Platforms/Wasm/WasmWebService.h Platforms/Wasm/WasmWebService.js Platforms/Wasm/default-library.js Platforms/Wasm/defaults.js Platforms/Wasm/stone-framework-loader.ts Platforms/Wasm/wasm-application.ts Platforms/Wasm/wasm-viewport.ts Platforms/WebAssembly/CMakeLists.txt Platforms/WebAssembly/Defaults.cpp Platforms/WebAssembly/Defaults.h Platforms/WebAssembly/WasmViewport.h Platforms/WebAssembly/WasmWebService.cpp Platforms/WebAssembly/WasmWebService.h Platforms/WebAssembly/WasmWebService.js Platforms/WebAssembly/default-library.js Platforms/WebAssembly/defaults.js Platforms/WebAssembly/stone-framework-loader.ts Platforms/WebAssembly/wasm-application.ts Platforms/WebAssembly/wasm-viewport.ts |
diffstat | 22 files changed, 1061 insertions(+), 1061 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/CMakeLists.txt Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,49 @@ +# Usage (Linux): +# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. + +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js") + +# Handling of memory +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912") # 512MB + resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize + +# To debug exceptions +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ORTHANC_SANDBOXED ON) +SET(ENABLE_SDL OFF) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) + + + + + + +# Regenerate a dummy "library.c" file each time the "library.js" file +# is modified, so as to force a new execution of the linking +add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/library.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" "" + DEPENDS "${CMAKE_SOURCE_DIR}/library.js")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.cpp Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,252 @@ +#include "Defaults.h" + +#include "WasmWebService.h" +#include <Framework/dev.h> +#include "Framework/Widgets/TestCairoWidget.h" +#include <Framework/Viewport/WidgetViewport.h> +#include <Framework/Widgets/LayerWidget.h> +#include <algorithm> + +static unsigned int width_ = 0; +static unsigned int height_ = 0; + +/**********************************/ + +static std::auto_ptr<OrthancStone::BasicWasmApplication> application; +static OrthancStone::ChangeObserver changeObserver_; +static OrthancStone::StatusBar statusBar_; + + +static std::list<std::shared_ptr<OrthancStone::WidgetViewport>> viewports_; + +std::shared_ptr<OrthancStone::WidgetViewport> FindViewportSharedPtr(ViewportHandle viewport) { + for (const auto& v : viewports_) { + if (v.get() == viewport) { + return v; + } + } + assert(false); + return std::shared_ptr<OrthancStone::WidgetViewport>(); +} + +#ifdef __cplusplus +extern "C" { +#endif + + using namespace OrthancStone; + + // when WASM needs a C++ viewport + ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() { + + std::shared_ptr<OrthancStone::WidgetViewport> viewport(new OrthancStone::WidgetViewport); + printf("viewport %x\n", viewport.get()); + + viewports_.push_back(viewport); + + printf("There are now %d viewports in C++\n", viewports_.size()); + + viewport->SetStatusBar(statusBar_); + viewport->Register(changeObserver_); + + return viewport.get(); + } + + // when WASM does not need a viewport anymore, it should release it + void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { + viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;}); + + printf("There are now %d viewports in C++\n", viewports_.size()); + } + + void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { + + printf("CreateWasmApplication\n"); + + application.reset(CreateUserApplication()); + + boost::program_options::options_description options; + application->DeclareStartupOptions(options); + } + + void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, + const char* value) { + application->SetStartupParameter(keyc, value); + } + + void EMSCRIPTEN_KEEPALIVE StartWasmApplication() { + + printf("StartWasmApplication\n"); + + // recreate a command line from uri arguments and parse it + boost::program_options::variables_map parameters; + application->GetStartupParameters(parameters); + + BasicWasmApplicationContext& context = dynamic_cast<BasicWasmApplicationContext&>(application->CreateApplicationContext(OrthancStone::WasmWebService::GetInstance(), NULL)); + application->Initialize(statusBar_, parameters); + +// viewport->SetSize(width_, height_); + printf("StartWasmApplication - completed\n"); + } + + void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent() + { + for (auto viewport : viewports_) { + // TODO Only launch the JavaScript timer if "HasUpdateContent()" + if (viewport->HasUpdateContent()) + { + viewport->UpdateContent(); + } + + } + + } + + + void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height) + { + width_ = width; + height_ = height; + + viewport->SetSize(width, height); + } + + int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport, + unsigned int width, + unsigned int height, + uint8_t* data) + { + changeObserver_.Reset(); + + //printf("ViewportRender called %dx%d\n", width, height); + if (width == 0 || + height == 0) + { + return 1; + } + + Orthanc::ImageAccessor surface; + surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data); + + viewport->Render(surface); + + // 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. + uint8_t* p = data; + for (unsigned int y = 0; y < height; y++) { + for (unsigned int x = 0; x < width; x++) { + uint8_t tmp = p[0]; + p[0] = p[2]; + p[2] = tmp; + + p += 4; + } + } + + return 1; + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport, + unsigned int rawButton, + int x, + int y, + unsigned int rawModifiers) + { + OrthancStone::MouseButton button; + switch (rawButton) + { + case 0: + button = OrthancStone::MouseButton_Left; + break; + + case 1: + button = OrthancStone::MouseButton_Middle; + break; + + case 2: + button = OrthancStone::MouseButton_Right; + break; + + default: + return; // Unknown button + } + + viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport, + int deltaY, + int x, + int y, + int isControl) + { + if (deltaY != 0) + { + OrthancStone::MouseWheelDirection direction = (deltaY < 0 ? + OrthancStone::MouseWheelDirection_Up : + OrthancStone::MouseWheelDirection_Down); + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + + if (isControl != 0) + { + modifiers = OrthancStone::KeyboardModifiers_Control; + } + + viewport->MouseWheel(direction, x, y, modifiers); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport, + int x, + int y) + { + viewport->MouseMove(x, y); + } + + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, + const char* key, + bool isShiftPressed, + bool isControlPressed, + bool isAltPressed) + + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (isShiftPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Shift); + } + if (isControlPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Control); + } + if (isAltPressed) { + modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Alt); + } + printf("key pressed : %c\n", key[0]); + viewport->KeyPressed(key[0], modifiers); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport) + { + viewport->MouseUp(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport) + { + viewport->MouseEnter(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport) + { + viewport->MouseLeave(); + } + + +#ifdef __cplusplus +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.h Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,74 @@ +#pragma once + +#include <emscripten/emscripten.h> + +#include <Framework/dev.h> +#include <Framework/Viewport/WidgetViewport.h> +#include <Framework/Widgets/LayerWidget.h> +#include <Framework/Widgets/LayoutWidget.h> +#include <Applications/Wasm/BasicWasmApplication.h> +#include <Applications/Wasm/BasicWasmApplicationContext.h> + +typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++ + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); + + // C++ methods accessible from JS + extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle); + +#ifdef __cplusplus +} +#endif + +extern OrthancStone::BasicWasmApplication* CreateUserApplication(); + +namespace OrthancStone { + + // default Ovserver to trigger Viewport redraw when something changes in the Viewport + class ChangeObserver : + public OrthancStone::IViewport::IObserver + { + private: + // Flag to avoid flooding JavaScript with redundant Redraw requests + bool isScheduled_; + + public: + ChangeObserver() : + isScheduled_(false) + { + } + + void Reset() + { + isScheduled_ = false; + } + + virtual void NotifyChange(const OrthancStone::IViewport &viewport) + { + if (!isScheduled_) + { + ScheduleWebViewportRedrawFromCpp((ViewportHandle)&viewport); // loosing constness when transmitted to Web + isScheduled_ = true; + } + } + }; + + // default status bar to log messages on the console/stdout + class StatusBar : public OrthancStone::IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + printf("%s\n", message.c_str()); + } + }; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmViewport.h Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,16 @@ +#pragma once + +#include <Framework/Viewport/WidgetViewport.h> + +#include <emscripten/emscripten.h> + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern OrthancStone::WidgetViewport* CreateWasmViewportFromCpp(const char* htmlCanvasId); + +#ifdef __cplusplus +} +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.cpp Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,90 @@ +#include "WasmWebService.h" + +#include <emscripten/emscripten.h> + +#ifdef __cplusplus +extern "C" { +#endif + + extern void WasmWebService_ScheduleGetRequest(void* callback, + const char* uri, + void* payload); + + extern void WasmWebService_SchedulePostRequest(void* callback, + const char* uri, + const void* body, + size_t bodySize, + void* payload); + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback, + const char* uri, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> + NotifyError(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback, + const char* uri, + const void* body, + size_t bodySize, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> + NotifySuccess(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); + } + } + +#ifdef __cplusplus +} +#endif + + + +namespace OrthancStone +{ + void WasmWebService::SetBaseUrl(const std::string base) + { + // Make sure the base url ends with "/" + if (base.empty() || + base[base.size() - 1] != '/') + { + base_ = base + "/"; + } + else + { + base_ = base; + } + } + + void WasmWebService::ScheduleGetRequest(ICallback& callback, + const std::string& uri, + Orthanc::IDynamicObject* payload) + { + std::string url = base_ + uri; + WasmWebService_ScheduleGetRequest(&callback, url.c_str(), payload); + } + + void WasmWebService::SchedulePostRequest(ICallback& callback, + const std::string& uri, + const std::string& body, + Orthanc::IDynamicObject* payload) + { + std::string url = base_ + uri; + WasmWebService_SchedulePostRequest(&callback, url.c_str(), + body.c_str(), body.size(), payload); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.h Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,44 @@ +#pragma once + +#include <Framework/Toolbox/IWebService.h> + +namespace OrthancStone +{ + class WasmWebService : public IWebService + { + private: + std::string base_; + + // Private constructor => Singleton design pattern + WasmWebService() : + base_("../../") + { + } + + public: + static WasmWebService& GetInstance() + { + static WasmWebService instance; + return instance; + } + + void SetBaseUrl(const std::string base); + + virtual void ScheduleGetRequest(ICallback& callback, + const std::string& uri, + Orthanc::IDynamicObject* payload); + + virtual void SchedulePostRequest(ICallback& callback, + const std::string& uri, + const std::string& body, + Orthanc::IDynamicObject* payload); + + virtual void Start() + { + } + + virtual void Stop() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.js Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,47 @@ +mergeInto(LibraryManager.library, { + WasmWebService_ScheduleGetRequest: function(callback, url, payload) { + // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data + // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ + var xhr = new XMLHttpRequest(); + var tmp = UTF8ToString(url); + xhr.open('GET', tmp, true); + xhr.responseType = 'arraybuffer'; + + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + // TODO - Is "new Uint8Array()" necessary? This copies the + // answer to the WebAssembly stack, hence necessitating + // increasing the TOTAL_STACK parameter of Emscripten + WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, tmp, payload); + } + } + } + + xhr.send(); + }, + + WasmWebService_SchedulePostRequest: function(callback, url, body, bodySize, payload) { + var xhr = new XMLHttpRequest(); + var tmp = UTF8ToString(url); + xhr.open('POST', tmp, true); + xhr.responseType = 'arraybuffer'; + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, tmp, payload); + } + } + } + + xhr.send(new Uint8ClampedArray(HEAPU8.buffer, body, bodySize)); + } +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/default-library.js Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,11 @@ +// this file contains the JS method you want to expose to C++ code + +mergeInto(LibraryManager.library, { + ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { + ScheduleWebViewportRedraw(cppViewportHandle); + }, + CreateWasmViewportFromCpp: function(htmlCanvasId) { + return CreateWasmViewport(htmlCanvasId); + } +}); + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/stone-framework-loader.ts Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,90 @@ +module Stone { + /** + * This file contains primitives to interface with WebAssembly and + * with the Stone framework. + **/ + + export declare type InitializationCallback = () => void; + + export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + + export class Framework + { + private static singleton_ : Framework = null; + + private constructor(verbose : boolean) + { + //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); + } + + + public ccall(name: string, + returnType: string, + argTypes: Array<string>, + argValues: Array<any>) : any + { + return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); + } + + + public cwrap(name: string, + returnType: string, + argTypes: Array<string>) : any + { + return StoneFrameworkModule.cwrap(name, returnType, argTypes); + } + + + public static GetInstance() : Framework + { + if (Framework.singleton_ == null) { + throw new Error('The WebAssembly module is not loaded yet'); + } else { + return Framework.singleton_; + } + } + + + public static Initialize(verbose: boolean, + callback: InitializationCallback) + { + console.log('Initializing WebAssembly'); + + (<any> window).StoneFrameworkModule = { + preRun: [ + function() { + console.log('Loading the Stone Framework using WebAssembly'); + } + ], + postRun: [ + function() { + // This function is called by ".js" wrapper once the ".wasm" + // WebAssembly module has been loaded and compiled by the + // browser + console.log('WebAssembly is ready'); + Framework.singleton_ = new Framework(verbose); + callback(); + } + ], + print: function(text : string) { + console.log(text); + }, + printErr: function(text : string) { + console.error(text); + }, + totalDependencies: 0 + }; + + // Dynamic loading of the JavaScript wrapper around WebAssembly + var script = document.createElement('script'); + script.type = 'application/javascript'; + script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.async = true; + document.head.appendChild(script); + } + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-application.ts Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,119 @@ +///<reference path='stone-framework-loader.ts'/> +///<reference path='wasm-viewport.ts'/> + +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} + +declare var StoneFrameworkModule : Stone.Framework; + +// global functions +var WasmWebService_NotifyError: Function = null; +var WasmWebService_NotifySuccess: Function = null; +var NotifyUpdateContent: Function = null; +var SetStartupParameter: Function = null; +var CreateWasmApplication: Function = null; +var CreateCppViewport: Function = null; +var ReleaseCppViewport: Function = null; +var StartWasmApplication: Function = null; + + +function UpdateContentThread() { + if (NotifyUpdateContent != null) { + NotifyUpdateContent(); + } + + setTimeout(UpdateContentThread, 100); // Update the viewport content every 100ms if need be +} + + +function GetUriParameters() { + var parameters = window.location.search.substr(1); + + if (parameters != null && + parameters != '') { + var result = {}; + var tokens = parameters.split('&'); + + for (var i = 0; i < tokens.length; i++) { + var tmp = tokens[i].split('='); + if (tmp.length == 2) { + result[tmp[0]] = decodeURIComponent(tmp[1]); + } + } + + return result; + } + else { + return {}; + } +} + +module Stone { + + // export declare type InitializationCallback = () => void; + + // export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + export class WasmApplication { + + private viewport_: WasmViewport; + private canvasId_: string; + + private pimpl_: any; // Private pointer to the underlying WebAssembly C++ object + + public constructor(canvasId: string) { + this.canvasId_ = canvasId; + //this.module_ = module; + } + } +} + + +function InitializeWasmApplication(canvasId: string): void { + + /************************************** */ + CreateWasmApplication(); + + // parse uri and transmit the parameters to the app before initializing it + var parameters = GetUriParameters(); + + for (var key in parameters) { + if (parameters.hasOwnProperty(key)) { + SetStartupParameter(key, parameters[key]); + } + } + + StartWasmApplication(); + /************************************** */ + + UpdateContentThread(); +} + +// Wait for the Orthanc Framework to be initialized (this initializes +// the WebAssembly environment) and then, create and initialize the Wasm application +Stone.Framework.Initialize(true, function () { + + console.log("Connecting C++ methods to JS methods"); + + SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); + CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); + CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); + ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); + StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['number']); + + WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); + WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []); + + // Prevent scrolling + document.body.addEventListener('touchmove', function (event) { + event.preventDefault(); + }, false); + + + InitializeWasmApplication("canvas"); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-viewport.ts Tue Jun 19 16:06:32 2018 +0200 @@ -0,0 +1,269 @@ +var isPendingRedraw = false; + +function ScheduleWebViewportRedraw(cppViewportHandle: any) : void +{ + if (!isPendingRedraw) { + isPendingRedraw = true; + console.log('Scheduling a refresh of the viewport, as its content changed'); + window.requestAnimationFrame(function() { + isPendingRedraw = false; + Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + }); + } +} + +declare function UTF8ToString(any): string; + +function CreateWasmViewport(htmlCanvasId: string) : any { + var cppViewportHandle = CreateCppViewport(); + var canvasId = UTF8ToString(htmlCanvasId); + var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted + webViewport.Initialize(); + + return cppViewportHandle; +} + +module Stone { + +// export declare type InitializationCallback = () => void; + +// export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + export class WasmViewport { + + private static cppWebViewportsMaps_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); + + private module_ : any; + private canvasId_ : string; + private htmlCanvas_ : HTMLCanvasElement; + private context_ : CanvasRenderingContext2D; + private imageData_ : any = null; + private renderingBuffer_ : any = null; + private touchZoom_ : any = false; + private touchTranslation_ : any = false; + + private ViewportSetSize : Function; + private ViewportRender : Function; + private ViewportMouseDown : Function; + private ViewportMouseMove : Function; + private ViewportMouseUp : Function; + private ViewportMouseEnter : Function; + private ViewportMouseLeave : Function; + private ViewportMouseWheel : Function; + private ViewportKeyPressed : Function; + + private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object + + public constructor(module: any, canvasId: string, cppViewport: any) { + + this.pimpl_ = cppViewport; + WasmViewport.cppWebViewportsMaps_[this.pimpl_] = this; + + this.module_ = module; + this.canvasId_ = canvasId; + this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement; + this.context_ = this.htmlCanvas_.getContext('2d'); + + this.ViewportSetSize = this.module_.cwrap('ViewportSetSize', null, [ 'number', 'number', 'number' ]); + this.ViewportRender = this.module_.cwrap('ViewportRender', null, [ 'number', 'number', 'number', 'number' ]); + this.ViewportMouseDown = this.module_.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportMouseMove = this.module_.cwrap('ViewportMouseMove', null, [ 'number', 'number', 'number' ]); + this.ViewportMouseUp = this.module_.cwrap('ViewportMouseUp', null, [ 'number' ]); + this.ViewportMouseEnter = this.module_.cwrap('ViewportMouseEnter', null, [ 'number' ]); + this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]); + this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'string', 'number', 'number' ]); + } + + public GetCppViewport() : number { + return this.pimpl_; + } + + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + if (WasmViewport.cppWebViewportsMaps_[cppViewportHandle] !== undefined) { + return WasmViewport.cppWebViewportsMaps_[cppViewportHandle]; + } + console.log("WasmViewport not found !"); + return undefined; + } + + public Redraw() { + if (this.imageData_ === null || + this.renderingBuffer_ === null || + this.ViewportRender(this.pimpl_, + this.imageData_.width, + this.imageData_.height, + this.renderingBuffer_) == 0) { + console.log('The rendering has failed'); + } else { + // Create an accessor to the rendering buffer (i.e. create a + // "window" above the heap of the WASM module), then copy it to + // the ImageData object + this.imageData_.data.set(new Uint8ClampedArray( + this.module_.buffer, + this.renderingBuffer_, + this.imageData_.width * this.imageData_.height * 4)); + + this.context_.putImageData(this.imageData_, 0, 0); + } + } + + public Resize() { + if (this.imageData_ != null && + (this.imageData_.width != window.innerWidth || + this.imageData_.height != window.innerHeight)) { + this.imageData_ = null; + } + + // width/height can be defined in percent of window width/height through html attributes like data-width-ratio="50" and data-height-ratio="20" + var widthRatio = Number(this.htmlCanvas_.dataset["widthRatio"]) || 100; + var heightRatio = Number(this.htmlCanvas_.dataset["heightRatio"]) || 100; + + this.htmlCanvas_.width = window.innerWidth * (widthRatio / 100); + this.htmlCanvas_.height = window.innerHeight * (heightRatio / 100); + + console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + + if (this.imageData_ === null) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } + + this.Redraw(); + } + + public Initialize() { + + // Force the rendering of the viewport for the first time + this.Resize(); + + var that : WasmViewport = this; + // Register an event listener to call the Resize() function + // each time the window is resized. + window.addEventListener('resize', function(event) { + that.Resize(); + }, false); + + this.htmlCanvas_.addEventListener('contextmenu', function(event) { + // Prevent right click on the canvas + event.preventDefault(); + }, false); + + this.htmlCanvas_.addEventListener('mouseleave', function(event) { + that.ViewportMouseLeave(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mouseenter', function(event) { + that.ViewportMouseEnter(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mousedown', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO */); + }); + + this.htmlCanvas_.addEventListener('mousemove', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseMove(that.pimpl_, x, y); + }); + + this.htmlCanvas_.addEventListener('mouseup', function(event) { + that.ViewportMouseUp(that.pimpl_); + }); + + window.addEventListener('keydown', function(event) { + that.ViewportKeyPressed(that.pimpl_, event.key, event.shiftKey, event.ctrlKey, event.altKey); + }); + + this.htmlCanvas_.addEventListener('wheel', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey); + event.preventDefault(); + }); + + this.htmlCanvas_.addEventListener('touchstart', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchend', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchmove', function(event) { + if (that.touchTranslation_.length == 2) { + var t = that.GetTouchTranslation(event); + that.ViewportMouseMove(that.pimpl_, t[0], t[1]); + } + else if (that.touchZoom_.length == 3) { + var z0 = that.touchZoom_; + var z1 = that.GetTouchZoom(event); + that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]); + } + else { + // Realize the gesture event + if (event.targetTouches.length == 1) { + // Exactly one finger inside the canvas => Setup a translation + that.touchTranslation_ = that.GetTouchTranslation(event); + that.ViewportMouseDown(that.pimpl_, + 1 /* middle button */, + that.touchTranslation_[0], + that.touchTranslation_[1], 0); + } else if (event.targetTouches.length == 2) { + // Exactly 2 fingers inside the canvas => Setup a pinch/zoom + that.touchZoom_ = that.GetTouchZoom(event); + var z0 = that.touchZoom_; + that.ViewportMouseDown(that.pimpl_, + 2 /* right button */, + z0[0], + z0[1], 0); + } + } + }); + } + + public ResetTouch() { + if (this.touchTranslation_ || + this.touchZoom_) { + this.ViewportMouseUp(this.pimpl_); + } + + this.touchTranslation_ = false; + this.touchZoom_ = false; + } + + public GetTouchTranslation(event) { + var touch = event.targetTouches[0]; + return [ + touch.pageX, + touch.pageY + ]; + } + + public GetTouchZoom(event) { + var touch1 = event.targetTouches[0]; + var touch2 = event.targetTouches[1]; + var dx = (touch1.pageX - touch2.pageX); + var dy = (touch1.pageY - touch2.pageY); + var d = Math.sqrt(dx * dx + dy * dy); + return [ + (touch1.pageX + touch2.pageX) / 2.0, + (touch1.pageY + touch2.pageY) / 2.0, + d + ]; + } + +} +} + \ No newline at end of file
--- a/Platforms/WebAssembly/CMakeLists.txt Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Usage (Linux): -# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. - -cmake_minimum_required(VERSION 2.8.3) - - -##################################################################### -## Configuration of the Emscripten compiler for WebAssembly target -##################################################################### - -set(WASM_FLAGS "-s WASM=1") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js") - -# Handling of memory -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912") # 512MB + resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize - -# To debug exceptions -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") - - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - -include(../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ORTHANC_SANDBOXED ON) -SET(ENABLE_SDL OFF) - -include(../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) - - - - - - -# Regenerate a dummy "library.c" file each time the "library.js" file -# is modified, so as to force a new execution of the linking -add_custom_command( - OUTPUT "${AUTOGENERATED_DIR}/library.c" - COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" "" - DEPENDS "${CMAKE_SOURCE_DIR}/library.js")
--- a/Platforms/WebAssembly/Defaults.cpp Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,252 +0,0 @@ -#include "Defaults.h" - -#include "WasmWebService.h" -#include <Framework/dev.h> -#include "Framework/Widgets/TestCairoWidget.h" -#include <Framework/Viewport/WidgetViewport.h> -#include <Framework/Widgets/LayerWidget.h> -#include <algorithm> - -static unsigned int width_ = 0; -static unsigned int height_ = 0; - -/**********************************/ - -static std::auto_ptr<OrthancStone::BasicWasmApplication> application; -static OrthancStone::ChangeObserver changeObserver_; -static OrthancStone::StatusBar statusBar_; - - -static std::list<std::shared_ptr<OrthancStone::WidgetViewport>> viewports_; - -std::shared_ptr<OrthancStone::WidgetViewport> FindViewportSharedPtr(ViewportHandle viewport) { - for (const auto& v : viewports_) { - if (v.get() == viewport) { - return v; - } - } - assert(false); - return std::shared_ptr<OrthancStone::WidgetViewport>(); -} - -#ifdef __cplusplus -extern "C" { -#endif - - using namespace OrthancStone; - - // when WASM needs a C++ viewport - ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() { - - std::shared_ptr<OrthancStone::WidgetViewport> viewport(new OrthancStone::WidgetViewport); - printf("viewport %x\n", viewport.get()); - - viewports_.push_back(viewport); - - printf("There are now %d viewports in C++\n", viewports_.size()); - - viewport->SetStatusBar(statusBar_); - viewport->Register(changeObserver_); - - return viewport.get(); - } - - // when WASM does not need a viewport anymore, it should release it - void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { - viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;}); - - printf("There are now %d viewports in C++\n", viewports_.size()); - } - - void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { - - printf("CreateWasmApplication\n"); - - application.reset(CreateUserApplication()); - - boost::program_options::options_description options; - application->DeclareStartupOptions(options); - } - - void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, - const char* value) { - application->SetStartupParameter(keyc, value); - } - - void EMSCRIPTEN_KEEPALIVE StartWasmApplication() { - - printf("StartWasmApplication\n"); - - // recreate a command line from uri arguments and parse it - boost::program_options::variables_map parameters; - application->GetStartupParameters(parameters); - - BasicWasmApplicationContext& context = dynamic_cast<BasicWasmApplicationContext&>(application->CreateApplicationContext(OrthancStone::WasmWebService::GetInstance(), NULL)); - application->Initialize(statusBar_, parameters); - -// viewport->SetSize(width_, height_); - printf("StartWasmApplication - completed\n"); - } - - void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent() - { - for (auto viewport : viewports_) { - // TODO Only launch the JavaScript timer if "HasUpdateContent()" - if (viewport->HasUpdateContent()) - { - viewport->UpdateContent(); - } - - } - - } - - - void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height) - { - width_ = width; - height_ = height; - - viewport->SetSize(width, height); - } - - int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport, - unsigned int width, - unsigned int height, - uint8_t* data) - { - changeObserver_.Reset(); - - //printf("ViewportRender called %dx%d\n", width, height); - if (width == 0 || - height == 0) - { - return 1; - } - - Orthanc::ImageAccessor surface; - surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data); - - viewport->Render(surface); - - // 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. - uint8_t* p = data; - for (unsigned int y = 0; y < height; y++) { - for (unsigned int x = 0; x < width; x++) { - uint8_t tmp = p[0]; - p[0] = p[2]; - p[2] = tmp; - - p += 4; - } - } - - return 1; - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport, - unsigned int rawButton, - int x, - int y, - unsigned int rawModifiers) - { - OrthancStone::MouseButton button; - switch (rawButton) - { - case 0: - button = OrthancStone::MouseButton_Left; - break; - - case 1: - button = OrthancStone::MouseButton_Middle; - break; - - case 2: - button = OrthancStone::MouseButton_Right; - break; - - default: - return; // Unknown button - } - - viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport, - int deltaY, - int x, - int y, - int isControl) - { - if (deltaY != 0) - { - OrthancStone::MouseWheelDirection direction = (deltaY < 0 ? - OrthancStone::MouseWheelDirection_Up : - OrthancStone::MouseWheelDirection_Down); - OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; - - if (isControl != 0) - { - modifiers = OrthancStone::KeyboardModifiers_Control; - } - - viewport->MouseWheel(direction, x, y, modifiers); - } - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport, - int x, - int y) - { - viewport->MouseMove(x, y); - } - - void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, - const char* key, - bool isShiftPressed, - bool isControlPressed, - bool isAltPressed) - - { - OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; - if (isShiftPressed) { - modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Shift); - } - if (isControlPressed) { - modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Control); - } - if (isAltPressed) { - modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Alt); - } - printf("key pressed : %c\n", key[0]); - viewport->KeyPressed(key[0], modifiers); - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport) - { - viewport->MouseUp(); - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport) - { - viewport->MouseEnter(); - } - - - void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport) - { - viewport->MouseLeave(); - } - - -#ifdef __cplusplus -} -#endif
--- a/Platforms/WebAssembly/Defaults.h Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -#pragma once - -#include <emscripten/emscripten.h> - -#include <Framework/dev.h> -#include <Framework/Viewport/WidgetViewport.h> -#include <Framework/Widgets/LayerWidget.h> -#include <Framework/Widgets/LayoutWidget.h> -#include <Applications/Wasm/BasicWasmApplication.h> -#include <Applications/Wasm/BasicWasmApplicationContext.h> - -typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++ - -#ifdef __cplusplus -extern "C" { -#endif - - // JS methods accessible from C++ - extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); - - // C++ methods accessible from JS - extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle); - -#ifdef __cplusplus -} -#endif - -extern OrthancStone::BasicWasmApplication* CreateUserApplication(); - -namespace OrthancStone { - - // default Ovserver to trigger Viewport redraw when something changes in the Viewport - class ChangeObserver : - public OrthancStone::IViewport::IObserver - { - private: - // Flag to avoid flooding JavaScript with redundant Redraw requests - bool isScheduled_; - - public: - ChangeObserver() : - isScheduled_(false) - { - } - - void Reset() - { - isScheduled_ = false; - } - - virtual void NotifyChange(const OrthancStone::IViewport &viewport) - { - if (!isScheduled_) - { - ScheduleWebViewportRedrawFromCpp((ViewportHandle)&viewport); // loosing constness when transmitted to Web - isScheduled_ = true; - } - } - }; - - // default status bar to log messages on the console/stdout - class StatusBar : public OrthancStone::IStatusBar - { - public: - virtual void ClearMessage() - { - } - - virtual void SetMessage(const std::string& message) - { - printf("%s\n", message.c_str()); - } - }; -} \ No newline at end of file
--- a/Platforms/WebAssembly/WasmViewport.h Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -#pragma once - -#include <Framework/Viewport/WidgetViewport.h> - -#include <emscripten/emscripten.h> - -#ifdef __cplusplus -extern "C" { -#endif - - // JS methods accessible from C++ - extern OrthancStone::WidgetViewport* CreateWasmViewportFromCpp(const char* htmlCanvasId); - -#ifdef __cplusplus -} -#endif \ No newline at end of file
--- a/Platforms/WebAssembly/WasmWebService.cpp Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -#include "WasmWebService.h" - -#include <emscripten/emscripten.h> - -#ifdef __cplusplus -extern "C" { -#endif - - extern void WasmWebService_ScheduleGetRequest(void* callback, - const char* uri, - void* payload); - - extern void WasmWebService_SchedulePostRequest(void* callback, - const char* uri, - const void* body, - size_t bodySize, - void* payload); - - void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback, - const char* uri, - void* payload) - { - if (callback == NULL) - { - throw; - } - else - { - reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> - NotifyError(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); - } - } - - void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback, - const char* uri, - const void* body, - size_t bodySize, - void* payload) - { - if (callback == NULL) - { - throw; - } - else - { - reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)-> - NotifySuccess(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); - } - } - -#ifdef __cplusplus -} -#endif - - - -namespace OrthancStone -{ - void WasmWebService::SetBaseUrl(const std::string base) - { - // Make sure the base url ends with "/" - if (base.empty() || - base[base.size() - 1] != '/') - { - base_ = base + "/"; - } - else - { - base_ = base; - } - } - - void WasmWebService::ScheduleGetRequest(ICallback& callback, - const std::string& uri, - Orthanc::IDynamicObject* payload) - { - std::string url = base_ + uri; - WasmWebService_ScheduleGetRequest(&callback, url.c_str(), payload); - } - - void WasmWebService::SchedulePostRequest(ICallback& callback, - const std::string& uri, - const std::string& body, - Orthanc::IDynamicObject* payload) - { - std::string url = base_ + uri; - WasmWebService_SchedulePostRequest(&callback, url.c_str(), - body.c_str(), body.size(), payload); - } -}
--- a/Platforms/WebAssembly/WasmWebService.h Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -#pragma once - -#include <Framework/Toolbox/IWebService.h> - -namespace OrthancStone -{ - class WasmWebService : public IWebService - { - private: - std::string base_; - - // Private constructor => Singleton design pattern - WasmWebService() : - base_("../../") - { - } - - public: - static WasmWebService& GetInstance() - { - static WasmWebService instance; - return instance; - } - - void SetBaseUrl(const std::string base); - - virtual void ScheduleGetRequest(ICallback& callback, - const std::string& uri, - Orthanc::IDynamicObject* payload); - - virtual void SchedulePostRequest(ICallback& callback, - const std::string& uri, - const std::string& body, - Orthanc::IDynamicObject* payload); - - virtual void Start() - { - } - - virtual void Stop() - { - } - }; -}
--- a/Platforms/WebAssembly/WasmWebService.js Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -mergeInto(LibraryManager.library, { - WasmWebService_ScheduleGetRequest: function(callback, url, payload) { - // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data - // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ - var xhr = new XMLHttpRequest(); - var tmp = UTF8ToString(url); - xhr.open('GET', tmp, true); - xhr.responseType = 'arraybuffer'; - - xhr.onreadystatechange = function() { - if (this.readyState == XMLHttpRequest.DONE) { - if (xhr.status === 200) { - // TODO - Is "new Uint8Array()" necessary? This copies the - // answer to the WebAssembly stack, hence necessitating - // increasing the TOTAL_STACK parameter of Emscripten - WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response), - this.response.byteLength, payload); - } else { - WasmWebService_NotifyError(callback, tmp, payload); - } - } - } - - xhr.send(); - }, - - WasmWebService_SchedulePostRequest: function(callback, url, body, bodySize, payload) { - var xhr = new XMLHttpRequest(); - var tmp = UTF8ToString(url); - xhr.open('POST', tmp, true); - xhr.responseType = 'arraybuffer'; - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - - xhr.onreadystatechange = function() { - if (this.readyState == XMLHttpRequest.DONE) { - if (xhr.status === 200) { - WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response), - this.response.byteLength, payload); - } else { - WasmWebService_NotifyError(callback, tmp, payload); - } - } - } - - xhr.send(new Uint8ClampedArray(HEAPU8.buffer, body, bodySize)); - } -});
--- a/Platforms/WebAssembly/default-library.js Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -// this file contains the JS method you want to expose to C++ code - -mergeInto(LibraryManager.library, { - ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { - ScheduleWebViewportRedraw(cppViewportHandle); - }, - CreateWasmViewportFromCpp: function(htmlCanvasId) { - return CreateWasmViewport(htmlCanvasId); - } -}); - \ No newline at end of file
--- a/Platforms/WebAssembly/stone-framework-loader.ts Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -module Stone { - /** - * This file contains primitives to interface with WebAssembly and - * with the Stone framework. - **/ - - export declare type InitializationCallback = () => void; - - export declare var StoneFrameworkModule : any; - - //const ASSETS_FOLDER : string = "assets/lib"; - //const WASM_FILENAME : string = "orthanc-framework"; - - - export class Framework - { - private static singleton_ : Framework = null; - - private constructor(verbose : boolean) - { - //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); - } - - - public ccall(name: string, - returnType: string, - argTypes: Array<string>, - argValues: Array<any>) : any - { - return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); - } - - - public cwrap(name: string, - returnType: string, - argTypes: Array<string>) : any - { - return StoneFrameworkModule.cwrap(name, returnType, argTypes); - } - - - public static GetInstance() : Framework - { - if (Framework.singleton_ == null) { - throw new Error('The WebAssembly module is not loaded yet'); - } else { - return Framework.singleton_; - } - } - - - public static Initialize(verbose: boolean, - callback: InitializationCallback) - { - console.log('Initializing WebAssembly'); - - (<any> window).StoneFrameworkModule = { - preRun: [ - function() { - console.log('Loading the Stone Framework using WebAssembly'); - } - ], - postRun: [ - function() { - // This function is called by ".js" wrapper once the ".wasm" - // WebAssembly module has been loaded and compiled by the - // browser - console.log('WebAssembly is ready'); - Framework.singleton_ = new Framework(verbose); - callback(); - } - ], - print: function(text : string) { - console.log(text); - }, - printErr: function(text : string) { - console.error(text); - }, - totalDependencies: 0 - }; - - // Dynamic loading of the JavaScript wrapper around WebAssembly - var script = document.createElement('script'); - script.type = 'application/javascript'; - script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; - script.async = true; - document.head.appendChild(script); - } - } -} \ No newline at end of file
--- a/Platforms/WebAssembly/wasm-application.ts Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -///<reference path='stone-framework-loader.ts'/> -///<reference path='wasm-viewport.ts'/> - -if (!('WebAssembly' in window)) { - alert('Sorry, your browser does not support WebAssembly :('); -} - -declare var StoneFrameworkModule : Stone.Framework; - -// global functions -var WasmWebService_NotifyError: Function = null; -var WasmWebService_NotifySuccess: Function = null; -var NotifyUpdateContent: Function = null; -var SetStartupParameter: Function = null; -var CreateWasmApplication: Function = null; -var CreateCppViewport: Function = null; -var ReleaseCppViewport: Function = null; -var StartWasmApplication: Function = null; - - -function UpdateContentThread() { - if (NotifyUpdateContent != null) { - NotifyUpdateContent(); - } - - setTimeout(UpdateContentThread, 100); // Update the viewport content every 100ms if need be -} - - -function GetUriParameters() { - var parameters = window.location.search.substr(1); - - if (parameters != null && - parameters != '') { - var result = {}; - var tokens = parameters.split('&'); - - for (var i = 0; i < tokens.length; i++) { - var tmp = tokens[i].split('='); - if (tmp.length == 2) { - result[tmp[0]] = decodeURIComponent(tmp[1]); - } - } - - return result; - } - else { - return {}; - } -} - -module Stone { - - // export declare type InitializationCallback = () => void; - - // export declare var StoneFrameworkModule : any; - - //const ASSETS_FOLDER : string = "assets/lib"; - //const WASM_FILENAME : string = "orthanc-framework"; - - export class WasmApplication { - - private viewport_: WasmViewport; - private canvasId_: string; - - private pimpl_: any; // Private pointer to the underlying WebAssembly C++ object - - public constructor(canvasId: string) { - this.canvasId_ = canvasId; - //this.module_ = module; - } - } -} - - -function InitializeWasmApplication(canvasId: string): void { - - /************************************** */ - CreateWasmApplication(); - - // parse uri and transmit the parameters to the app before initializing it - var parameters = GetUriParameters(); - - for (var key in parameters) { - if (parameters.hasOwnProperty(key)) { - SetStartupParameter(key, parameters[key]); - } - } - - StartWasmApplication(); - /************************************** */ - - UpdateContentThread(); -} - -// Wait for the Orthanc Framework to be initialized (this initializes -// the WebAssembly environment) and then, create and initialize the Wasm application -Stone.Framework.Initialize(true, function () { - - console.log("Connecting C++ methods to JS methods"); - - SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); - CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); - CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); - ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); - StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['number']); - - WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); - WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); - NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []); - - // Prevent scrolling - document.body.addEventListener('touchmove', function (event) { - event.preventDefault(); - }, false); - - - InitializeWasmApplication("canvas"); -}); \ No newline at end of file
--- a/Platforms/WebAssembly/wasm-viewport.ts Tue Jun 19 16:02:41 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,269 +0,0 @@ -var isPendingRedraw = false; - -function ScheduleWebViewportRedraw(cppViewportHandle: any) : void -{ - if (!isPendingRedraw) { - isPendingRedraw = true; - console.log('Scheduling a refresh of the viewport, as its content changed'); - window.requestAnimationFrame(function() { - isPendingRedraw = false; - Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); - }); - } -} - -declare function UTF8ToString(any): string; - -function CreateWasmViewport(htmlCanvasId: string) : any { - var cppViewportHandle = CreateCppViewport(); - var canvasId = UTF8ToString(htmlCanvasId); - var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted - webViewport.Initialize(); - - return cppViewportHandle; -} - -module Stone { - -// export declare type InitializationCallback = () => void; - -// export declare var StoneFrameworkModule : any; - - //const ASSETS_FOLDER : string = "assets/lib"; - //const WASM_FILENAME : string = "orthanc-framework"; - - export class WasmViewport { - - private static cppWebViewportsMaps_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); - - private module_ : any; - private canvasId_ : string; - private htmlCanvas_ : HTMLCanvasElement; - private context_ : CanvasRenderingContext2D; - private imageData_ : any = null; - private renderingBuffer_ : any = null; - private touchZoom_ : any = false; - private touchTranslation_ : any = false; - - private ViewportSetSize : Function; - private ViewportRender : Function; - private ViewportMouseDown : Function; - private ViewportMouseMove : Function; - private ViewportMouseUp : Function; - private ViewportMouseEnter : Function; - private ViewportMouseLeave : Function; - private ViewportMouseWheel : Function; - private ViewportKeyPressed : Function; - - private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object - - public constructor(module: any, canvasId: string, cppViewport: any) { - - this.pimpl_ = cppViewport; - WasmViewport.cppWebViewportsMaps_[this.pimpl_] = this; - - this.module_ = module; - this.canvasId_ = canvasId; - this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement; - this.context_ = this.htmlCanvas_.getContext('2d'); - - this.ViewportSetSize = this.module_.cwrap('ViewportSetSize', null, [ 'number', 'number', 'number' ]); - this.ViewportRender = this.module_.cwrap('ViewportRender', null, [ 'number', 'number', 'number', 'number' ]); - this.ViewportMouseDown = this.module_.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number', 'number' ]); - this.ViewportMouseMove = this.module_.cwrap('ViewportMouseMove', null, [ 'number', 'number', 'number' ]); - this.ViewportMouseUp = this.module_.cwrap('ViewportMouseUp', null, [ 'number' ]); - this.ViewportMouseEnter = this.module_.cwrap('ViewportMouseEnter', null, [ 'number' ]); - this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]); - this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]); - this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'string', 'number', 'number' ]); - } - - public GetCppViewport() : number { - return this.pimpl_; - } - - public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { - if (WasmViewport.cppWebViewportsMaps_[cppViewportHandle] !== undefined) { - return WasmViewport.cppWebViewportsMaps_[cppViewportHandle]; - } - console.log("WasmViewport not found !"); - return undefined; - } - - public Redraw() { - if (this.imageData_ === null || - this.renderingBuffer_ === null || - this.ViewportRender(this.pimpl_, - this.imageData_.width, - this.imageData_.height, - this.renderingBuffer_) == 0) { - console.log('The rendering has failed'); - } else { - // Create an accessor to the rendering buffer (i.e. create a - // "window" above the heap of the WASM module), then copy it to - // the ImageData object - this.imageData_.data.set(new Uint8ClampedArray( - this.module_.buffer, - this.renderingBuffer_, - this.imageData_.width * this.imageData_.height * 4)); - - this.context_.putImageData(this.imageData_, 0, 0); - } - } - - public Resize() { - if (this.imageData_ != null && - (this.imageData_.width != window.innerWidth || - this.imageData_.height != window.innerHeight)) { - this.imageData_ = null; - } - - // width/height can be defined in percent of window width/height through html attributes like data-width-ratio="50" and data-height-ratio="20" - var widthRatio = Number(this.htmlCanvas_.dataset["widthRatio"]) || 100; - var heightRatio = Number(this.htmlCanvas_.dataset["heightRatio"]) || 100; - - this.htmlCanvas_.width = window.innerWidth * (widthRatio / 100); - this.htmlCanvas_.height = window.innerHeight * (heightRatio / 100); - - console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); - - if (this.imageData_ === null) { - this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); - this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); - - if (this.renderingBuffer_ != null) { - this.module_._free(this.renderingBuffer_); - } - - this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); - } - - this.Redraw(); - } - - public Initialize() { - - // Force the rendering of the viewport for the first time - this.Resize(); - - var that : WasmViewport = this; - // Register an event listener to call the Resize() function - // each time the window is resized. - window.addEventListener('resize', function(event) { - that.Resize(); - }, false); - - this.htmlCanvas_.addEventListener('contextmenu', function(event) { - // Prevent right click on the canvas - event.preventDefault(); - }, false); - - this.htmlCanvas_.addEventListener('mouseleave', function(event) { - that.ViewportMouseLeave(that.pimpl_); - }); - - this.htmlCanvas_.addEventListener('mouseenter', function(event) { - that.ViewportMouseEnter(that.pimpl_); - }); - - this.htmlCanvas_.addEventListener('mousedown', function(event) { - var x = event.pageX - this.offsetLeft; - var y = event.pageY - this.offsetTop; - that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO */); - }); - - this.htmlCanvas_.addEventListener('mousemove', function(event) { - var x = event.pageX - this.offsetLeft; - var y = event.pageY - this.offsetTop; - that.ViewportMouseMove(that.pimpl_, x, y); - }); - - this.htmlCanvas_.addEventListener('mouseup', function(event) { - that.ViewportMouseUp(that.pimpl_); - }); - - window.addEventListener('keydown', function(event) { - that.ViewportKeyPressed(that.pimpl_, event.key, event.shiftKey, event.ctrlKey, event.altKey); - }); - - this.htmlCanvas_.addEventListener('wheel', function(event) { - var x = event.pageX - this.offsetLeft; - var y = event.pageY - this.offsetTop; - that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey); - event.preventDefault(); - }); - - this.htmlCanvas_.addEventListener('touchstart', function(event) { - that.ResetTouch(); - }); - - this.htmlCanvas_.addEventListener('touchend', function(event) { - that.ResetTouch(); - }); - - this.htmlCanvas_.addEventListener('touchmove', function(event) { - if (that.touchTranslation_.length == 2) { - var t = that.GetTouchTranslation(event); - that.ViewportMouseMove(that.pimpl_, t[0], t[1]); - } - else if (that.touchZoom_.length == 3) { - var z0 = that.touchZoom_; - var z1 = that.GetTouchZoom(event); - that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]); - } - else { - // Realize the gesture event - if (event.targetTouches.length == 1) { - // Exactly one finger inside the canvas => Setup a translation - that.touchTranslation_ = that.GetTouchTranslation(event); - that.ViewportMouseDown(that.pimpl_, - 1 /* middle button */, - that.touchTranslation_[0], - that.touchTranslation_[1], 0); - } else if (event.targetTouches.length == 2) { - // Exactly 2 fingers inside the canvas => Setup a pinch/zoom - that.touchZoom_ = that.GetTouchZoom(event); - var z0 = that.touchZoom_; - that.ViewportMouseDown(that.pimpl_, - 2 /* right button */, - z0[0], - z0[1], 0); - } - } - }); - } - - public ResetTouch() { - if (this.touchTranslation_ || - this.touchZoom_) { - this.ViewportMouseUp(this.pimpl_); - } - - this.touchTranslation_ = false; - this.touchZoom_ = false; - } - - public GetTouchTranslation(event) { - var touch = event.targetTouches[0]; - return [ - touch.pageX, - touch.pageY - ]; - } - - public GetTouchZoom(event) { - var touch1 = event.targetTouches[0]; - var touch2 = event.targetTouches[1]; - var dx = (touch1.pageX - touch2.pageX); - var dy = (touch1.pageY - touch2.pageY); - var d = Math.sqrt(dx * dx + dy * dy); - return [ - (touch1.pageX + touch2.pageX) / 2.0, - (touch1.pageY + touch2.pageY) / 2.0, - d - ]; - } - -} -} - \ No newline at end of file