# HG changeset patch # User am@osimis.io # Date 1528966622 -7200 # Node ID d30a10d574ec0c433d3f18282af2d49386acee93 # Parent 84844649a8fde92971d1289f1a771535bfbb478c refactoring continued - not working diff -r 84844649a8fd -r d30a10d574ec Platforms/WebAssembly/Defaults.cpp --- a/Platforms/WebAssembly/Defaults.cpp Tue Jun 12 17:21:15 2018 +0200 +++ b/Platforms/WebAssembly/Defaults.cpp Thu Jun 14 10:57:02 2018 +0200 @@ -1,2 +1,281 @@ #include "Defaults.h" +#include "WasmWebService.h" +#include +#include "Framework/Widgets/TestCairoWidget.h" +#include +#include + +static unsigned int width_ = 0; +static unsigned int height_ = 0; + +/**********************************/ + +static std::auto_ptr application; +static std::shared_ptr viewport_; +static OrthancStone::ChangeObserver changeObserver_; +static OrthancStone::StatusBar statusBar_; + + +#ifdef __cplusplus +extern "C" { +#endif + + using namespace OrthancStone; + + void EMSCRIPTEN_KEEPALIVE CreateWasmApplication() { + + printf("CreateWasmApplication\n"); + viewport_.reset(new OrthancStone::WidgetViewport); + viewport_->SetStatusBar(statusBar_); + viewport_->Register(changeObserver_); + + + 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(application->CreateApplicationContext(OrthancStone::WasmWebService::GetInstance(), viewport_));; + application->Initialize(statusBar_, parameters); + + viewport_->SetSize(width_, height_); + printf("StartWasmApplication - completed\n"); + } + + // void EMSCRIPTEN_KEEPALIVE ViewportUpdate(const char* _instanceId) { + // printf("updating viewport content, Instance = [%s]\n", instanceId.c_str()); + + // layerSource->LoadFrame(instanceId, 0); + // printf("frame loaded\n"); + // instanceWidget->UpdateContent(); + + // printf("update should be done\n"); + // } + + // void EMSCRIPTEN_KEEPALIVE ViewportStart() + // { + + // viewport_.reset(new OrthancStone::WidgetViewport); + // viewport_->SetStatusBar(statusBar_); + // viewport_->Register(changeObserver_); + // instanceWidget.reset(new OrthancStone::LayerWidget); + // layerSource = new OrthancStone::OrthancFrameLayerSource(OrthancStone::WasmWebService::GetInstance()); + + // if (!instanceId.empty()) + // { + // layerSource->LoadFrame(instanceId, 0); + // } else { + // printf("No instance provided so far\n"); + // } + + // instanceWidget->AddLayer(layerSource); + + // { + // OrthancStone::RenderStyle s; + // //s.drawGrid_ = true; + // s.alpha_ = 1; + // s.windowing_ = OrthancStone::ImageWindowing_Bone; + // instanceWidget->SetLayerStyle(0, s); + // } + + // viewport_->SetCentralWidget(instanceWidget.release()); + // viewport_->SetSize(width_, height_); + + + // } + + void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent() + { + // TODO Only launch the JavaScript timer if "HasUpdateContent()" + if (viewport_.get() != NULL && + viewport_->HasUpdateContent()) + { + viewport_->UpdateContent(); + } + + } + + + void EMSCRIPTEN_KEEPALIVE ViewportSetSize(unsigned int width, unsigned int height) + { + width_ = width; + height_ = height; + + if (viewport_.get() != NULL) + { + viewport_->SetSize(width, height); + } + } + + int EMSCRIPTEN_KEEPALIVE ViewportRender(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); + + if (viewport_.get() != NULL) + { + 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(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 + } + + if (viewport_.get() != NULL) + { + viewport_->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(int deltaY, + int x, + int y, + int isControl) + { + if (viewport_.get() != NULL && + 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(int x, + int y) + { + if (viewport_.get() != NULL) + { + viewport_->MouseMove(x, y); + } + } + + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(const char* key, + bool isShiftPressed, + bool isControlPressed, + bool isAltPressed) + + { + if (viewport_.get() != NULL) + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (isShiftPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Shift); + } + if (isControlPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Control); + } + if (isAltPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Alt); + } + printf("key pressed : %c\n", key[0]); + viewport_->KeyPressed(key[0], modifiers); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseUp() + { + if (viewport_.get() != NULL) + { + viewport_->MouseUp(); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter() + { + if (viewport_.get() != NULL) + { + viewport_->MouseEnter(); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave() + { + if (viewport_.get() != NULL) + { + viewport_->MouseLeave(); + } + } + + +#ifdef __cplusplus +} +#endif diff -r 84844649a8fd -r d30a10d574ec Platforms/WebAssembly/Defaults.h --- a/Platforms/WebAssembly/Defaults.h Tue Jun 12 17:21:15 2018 +0200 +++ b/Platforms/WebAssembly/Defaults.h Thu Jun 14 10:57:02 2018 +0200 @@ -1,23 +1,35 @@ #pragma once +#include + #include #include #include #include +#include +#include #ifdef __cplusplus extern "C" { #endif + // JS methods accessible from C++ extern void ScheduleRedraw(); + // C++ methods accessible from JS + extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(); +// extern void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, const char* value); +// extern void EMSCRIPTEN_KEEPALIVE StartWasmApplication(); + #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 { @@ -46,7 +58,7 @@ } }; - + // default status bar to log messages on the console/stdout class StatusBar : public OrthancStone::IStatusBar { public: diff -r 84844649a8fd -r d30a10d574ec Platforms/WebAssembly/stone-framework-loader.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/WebAssembly/stone-framework-loader.ts Thu Jun 14 10:57:02 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, + argValues: Array) : any + { + return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); + } + + + public cwrap(name: string, + returnType: string, + argTypes: Array) : 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'); + + ( 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 diff -r 84844649a8fd -r d30a10d574ec Platforms/WebAssembly/wasm-application.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/WebAssembly/wasm-application.js Thu Jun 14 10:57:02 2018 +0200 @@ -0,0 +1,102 @@ + + +// Global context used by "library.js" +var viewport = null; +var WasmWebService_NotifyError = null; +var WasmWebService_NotifySuccess = null; +//var NotifyRestApiGet = null; +var NotifyUpdateContent = 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 {}; + } +} + + + +function InitializeWasmApplication(canvasId) +{ + console.log("Initializing wasm-app"); + viewport = WebAssemblyViewport(StoneFrameworkModule, 'canvas'); + + /******************** */ + SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, [ 'string', 'string' ]); + CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, [ ], [ ]); + StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, [ ], [ ]); + + /******************** */ + + // NotifyGlobalParameter = StoneFrameworkModule.cwrap('NotifyGlobalParameter', null, + // [ 'string', 'string' ]); + // ViewportUpdate = StoneFrameworkModule.cwrap('ViewportUpdate', null, + // [ 'string' ]); + WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, + [ 'number', 'string', 'array', 'number', 'number' ]); + WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, + [ 'number', 'string', 'number' ]); + //NotifyRestApiGet = Module.cwrap('NotifyRestApiGet', null, [ 'number', 'array', 'number' ]); + NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, [ ]); + + // Prevent scrolling + document.body.addEventListener('touchmove', function(event) { + event.preventDefault(); + }, false); + + document.getElementById('canvas').onclick = function() { + viewport.Redraw(); + }; + + + /************************************** */ + 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(); +} + +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} else { + +} diff -r 84844649a8fd -r d30a10d574ec Platforms/WebAssembly/wasm-viewport.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/WebAssembly/wasm-viewport.js Thu Jun 14 10:57:02 2018 +0200 @@ -0,0 +1,197 @@ +// http://stackoverflow.com/a/28900478/881731 + +function WebAssemblyViewport(module, canvasId) { + this.module = module; + + // Obtain a reference to the canvas element using its id + this.htmlCanvas = document.getElementById(canvasId); + + // Obtain a graphics context on the canvas element for drawing. + this.context = htmlCanvas.getContext('2d'); + + // ImageData structure that can be used to update the content of the canvas + this.imageData = null; + + // Temporary buffer in the WebAssembly heap that is used to render the pixels + this.renderingBuffer = null; + + // Get access to the WebAssembly functions + this.ViewportSetSize = this.module.cwrap('ViewportSetSize', null, [ 'number', 'number' ]); + this.ViewportRender = this.module.cwrap('ViewportRender', 'number', [ 'number', 'number', 'number' ]); + this.ViewportMouseDown = this.module.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number' ]); + this.ViewportMouseMove = this.module.cwrap('ViewportMouseMove', null, [ 'number', 'number' ]); + this.ViewportMouseUp = this.module.cwrap('ViewportMouseUp', null, [ ]); + this.ViewportMouseEnter = this.module.cwrap('ViewportMouseEnter', null, []); + this.ViewportMouseLeave = this.module.cwrap('ViewportMouseLeave', null, []); + this.ViewportMouseWheel = this.module.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number' ]); + this.ViewportKeyPressed = this.module.cwrap('ViewportKeyPressed', null, [ 'string', 'number', 'number' ]); + + this.Redraw = function() { + if (this.imageData === null || + this.renderingBuffer === null || + ViewportRender(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(imageData, 0, 0); + } + } + + this.Resize = function() { + if (this.imageData != null && + (this.imageData.width != this.window.innerWidth || + this.imageData.height != this.window.innerHeight)) { + this.imageData = null; + } + + this.htmlCanvas.width = window.innerWidth; + this.htmlCanvas.height = window.innerHeight; + + if (this.imageData === null) { + this.imageData = context.getImageData(0, 0, this.htmlCanvas.width, this.htmlCanvas.height); + ViewportSetSize(this.htmlCanvas.width, this.htmlCanvas.height); + + if (this.renderingBuffer != null) { + this.module._free(this.renderingBuffer); + } + + renderingBuffer = this.module._malloc(this.imageData.width * this.imageData.height * 4); + } + + this.Redraw(); + } + + // Force the rendering of the viewport for the first time + this.Resize(); + + // Register an event listener to call the Resize() function + // each time the window is resized. + window.addEventListener('resize', this.Resize, false); + + var that = this; + + this.htmlCanvas.addEventListener('contextmenu', function(event) { + // Prevent right click on the canvas + event.preventDefault(); + }, false); + + this.htmlCanvas.addEventListener('mouseleave', function(event) { + that.ViewportMouseLeave(); + }); + + this.htmlCanvas.addEventListener('mouseenter', function(event) { + that.ViewportMouseEnter(); + }); + + this.htmlCanvas.addEventListener('mousedown', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseDown(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(x, y); + }); + + this.htmlCanvas.addEventListener('mouseup', function(event) { + that.ViewportMouseUp(); + }); + + window.addEventListener('keydown', function(event) { + that.ViewportKeyPressed(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(event.deltaY, x, y, event.ctrlKey); + event.preventDefault(); + }); + + + // Touch events + this.touchTranslation = false; + this.touchZoom = false; + + this.ResetTouch = function() { + if (this.touchTranslation || + this.touchZoom) { + this.ViewportMouseUp(); + } + + this.touchTranslation = false; + this.touchZoom = false; + } + + this.GetTouchTranslation = function(event) { + var touch = event.targetTouches[0]; + return [ + touch.pageX, + touch.pageY + ]; + } + + this.GetTouchZoom = function(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 + ]; + } + + this.htmlCanvas.addEventListener('touchstart', function(event) { + ResetTouch(); + }); + + this.htmlCanvas.addEventListener('touchend', function(event) { + ResetTouch(); + }); + + this.htmlCanvas.addEventListener('touchmove', function(event) { + if (that.touchTranslation.length == 2) { + var t = GetTouchTranslation(event); + that.ViewportMouseMove(t[0], t[1]); + } + else if (that.touchZoom.length == 3) { + var z0 = that.touchZoom; + var z1 = GetTouchZoom(event); + that.ViewportMouseMove(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 = GetTouchTranslation(event); + that.ViewportMouseDown(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 = GetTouchZoom(event); + var z0 = that.touchZoom; + that.ViewportMouseDown(2 /* right button */, + z0[0], + z0[1], 0); + } + } + }); + + return this; + } + \ No newline at end of file