Mercurial > hg > orthanc-stone
view Platforms/Wasm/wasm-viewport.ts @ 1327:4f8db2d202c8 broker
OrthancSeriesProgressiveLoader now has two modes that
can be selected at object creation :
- progressive (will first load jpeg50, then jpeg90 then PAM)
- non-progressive (will directly load PAM (uncompressed))
Please note that the slice loading order remains dynamic
and depending upon the slice that the client code wishes
to extract from the volume.
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 25 Mar 2020 14:34:27 +0100 |
parents | 67d0a8da4afe |
children |
line wrap: on
line source
import * as wasmApplicationRunner from './wasm-application-runner' import * as Logger from './logger' var isPendingRedraw = false; function ScheduleWebViewportRedraw(cppViewportHandle: any) : void { if (!isPendingRedraw) { isPendingRedraw = true; Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; let viewport = WasmViewport.GetFromCppViewport(cppViewportHandle); if (viewport) { viewport.Redraw(); } }); } } (<any>window).ScheduleWebViewportRedraw = ScheduleWebViewportRedraw; declare function UTF8ToString(v: any): string; function CreateWasmViewport(htmlCanvasId: string) : any { var cppViewportHandle = wasmApplicationRunner.CreateCppViewport(); var canvasId = UTF8ToString(htmlCanvasId); var webViewport = new WasmViewport((<any> window).StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted webViewport.Initialize(); return cppViewportHandle; } (<any>window).CreateWasmViewport = CreateWasmViewport; export class WasmViewport { private static viewportsMapByCppHandle_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); // key = the C++ handle private static viewportsMapByCanvasId_ : Map<string, WasmViewport> = new Map<string, WasmViewport>(); // key = the canvasId private module_ : any; private canvasId_ : string; private htmlCanvas_ : HTMLCanvasElement; private context_ : CanvasRenderingContext2D | null; private imageData_ : any = null; private renderingBuffer_ : any = null; private touchGestureInProgress_: boolean = false; private touchCount_: number = 0; private touchGestureLastCoordinates_: [number, number][] = []; // last x,y coordinates of each touch 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 ViewportTouchStart : Function; private ViewportTouchMove : Function; private ViewportTouchEnd : Function; private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object public constructor(module: any, canvasId: string, cppViewport: any) { this.pimpl_ = cppViewport; WasmViewport.viewportsMapByCppHandle_[this.pimpl_] = this; WasmViewport.viewportsMapByCanvasId_[canvasId] = this; this.module_ = module; this.canvasId_ = canvasId; this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement; if (this.htmlCanvas_ == null) { Logger.defaultLogger.error("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'"); } 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', 'number', 'string', 'number', 'number' ]); this.ViewportTouchStart = this.module_.cwrap('ViewportTouchStart', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); this.ViewportTouchMove = this.module_.cwrap('ViewportTouchMove', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); this.ViewportTouchEnd = this.module_.cwrap('ViewportTouchEnd', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); } public GetCppViewport() : number { return this.pimpl_; } public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport | null { if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) { return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle]; } Logger.defaultLogger.error("WasmViewport not found !"); return null; } public static GetFromCanvasId(canvasId: string) : WasmViewport | null { if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) { return WasmViewport.viewportsMapByCanvasId_[canvasId]; } Logger.defaultLogger.error("WasmViewport not found !"); return null; } public static ResizeAll() { for (let canvasId in WasmViewport.viewportsMapByCanvasId_) { WasmViewport.viewportsMapByCanvasId_[canvasId].Resize(); } } public Redraw() { if (this.imageData_ === null || this.renderingBuffer_ === null || this.ViewportRender(this.pimpl_, this.imageData_.width, this.imageData_.height, this.renderingBuffer_) == 0) { Logger.defaultLogger.error('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_.HEAPU8.buffer, this.renderingBuffer_, this.imageData_.width * this.imageData_.height * 4)); if (this.context_) { 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 is defined by the parent width/height if (this.htmlCanvas_.parentElement) { this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; Logger.defaultLogger.debug("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); if (this.imageData_ === null && this.context_) { this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); if (this.renderingBuffer_ != null) { this.module_._free(this.renderingBuffer_); } this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); } else { this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); } this.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 detect modifier keys*/); }); 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) { var keyChar: string | null = event.key; var keyCode = event.keyCode if (keyChar.length == 1) { keyCode = 0; // maps to OrthancStone::KeyboardKeys_Generic } else { keyChar = null; } // console.log("key: ", keyCode, keyChar); that.ViewportKeyPressed(that.pimpl_, keyCode, keyChar, 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(); }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface this.htmlCanvas_.addEventListener('touchstart', function(event: TouchEvent) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); // TODO: find a way to pass the coordinates as an array between JS and C++ var x0 = 0; var y0 = 0; var x1 = 0; var y1 = 0; var x2 = 0; var y2 = 0; if (event.targetTouches.length > 0) { x0 = event.targetTouches[0].pageX; y0 = event.targetTouches[0].pageY; } if (event.targetTouches.length > 1) { x1 = event.targetTouches[1].pageX; y1 = event.targetTouches[1].pageY; } if (event.targetTouches.length > 2) { x2 = event.targetTouches[2].pageX; y2 = event.targetTouches[2].pageY; } that.ViewportTouchStart(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface this.htmlCanvas_.addEventListener('touchend', function(event) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); // TODO: find a way to pass the coordinates as an array between JS and C++ var x0 = 0; var y0 = 0; var x1 = 0; var y1 = 0; var x2 = 0; var y2 = 0; if (event.targetTouches.length > 0) { x0 = event.targetTouches[0].pageX; y0 = event.targetTouches[0].pageY; } if (event.targetTouches.length > 1) { x1 = event.targetTouches[1].pageX; y1 = event.targetTouches[1].pageY; } if (event.targetTouches.length > 2) { x2 = event.targetTouches[2].pageX; y2 = event.targetTouches[2].pageY; } that.ViewportTouchEnd(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); }); this.htmlCanvas_.addEventListener('touchmove', function(event: TouchEvent) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); // TODO: find a way to pass the coordinates as an array between JS and C++ var x0 = 0; var y0 = 0; var x1 = 0; var y1 = 0; var x2 = 0; var y2 = 0; if (event.targetTouches.length > 0) { x0 = event.targetTouches[0].pageX; y0 = event.targetTouches[0].pageY; } if (event.targetTouches.length > 1) { x1 = event.targetTouches[1].pageX; y1 = event.targetTouches[1].pageY; } if (event.targetTouches.length > 2) { x2 = event.targetTouches[2].pageX; y2 = event.targetTouches[2].pageY; } that.ViewportTouchMove(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); return; }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface } public ResetTouch() { if (this.touchTranslation_ || this.touchZoom_) { this.ViewportMouseUp(this.pimpl_); } this.touchTranslation_ = false; this.touchZoom_ = false; } public GetTouchTranslation(event: any) { var touch = event.targetTouches[0]; return [ touch.pageX, touch.pageY ]; } public GetTouchZoom(event: any) { 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 ]; } }