/** * HTML module. * @module html */ var dwv = dwv || {}; dwv.html = dwv.html || {}; /** * Window layer. * @class Layer * @namespace dwv.html * @constructor * @param {String} name The name of the layer. */ dwv.html.Layer = function(name) { /** * The associated HTMLCanvasElement. * @property canvas * @private * @type Object */ var canvas = null; /** * A cache of the initial canvas. * @property cacheCanvas * @private * @type Object */ var cacheCanvas = null; /** * The associated CanvasRenderingContext2D. * @property context * @private * @type Object */ var context = null; /** * Get the layer name. * @method getName * @return {String} The layer name. */ this.getName = function() { return name; }; /** * Get the layer canvas. * @method getCanvas * @return {Object} The layer canvas. */ this.getCanvas = function() { return canvas; }; /** * Get the layer context. * @method getContext * @return {Object} The layer context. */ this.getContext = function() { return context; }; /** * Get the layer offset on page. * @method getOffset * @return {Number} The layer offset on page. */ this.getOffset = function() { return $('#'+name).offset(); }; /** * The image data array. * @property imageData * @private * @type Array */ var imageData = null; /** * The layer origin. * @property origin * @private * @type {Object} */ var origin = {'x': 0, 'y': 0}; /** * Get the layer origin. * @method getOrigin * @returns {Object} The layer origin as {'x','y'}. */ this.getOrigin = function () { return origin; }; /** * The image zoom. * @property zoom * @private * @type {Object} */ var zoom = {'x': 1, 'y': 1}; /** * Get the layer zoom. * @method getZoom * @returns {Object} The layer zoom as {'x','y'}. */ this.getZoom = function () { return zoom; }; /** * Set the canvas width. * @method setWidth * @param {Number} width The new width. */ this.setWidth = function ( width ) { canvas.width = width; }; /** * Set the canvas height. * @method setHeight * @param {Number} height The new height. */ this.setHeight = function ( height ) { canvas.height = height; }; /** * Set the layer zoom. * @method setZoom * @param {Number} newZoomX The zoom in the X direction. * @param {Number} newZoomY The zoom in the Y direction. * @param {Number} centerX The zoom center in the X direction. * @param {Number} centerY The zoom center in the Y direction. */ this.zoom = function(newZoomX,newZoomY,centerX,centerY) { // The zoom is the ratio between the differences from the center // to the origins: // centerX - originX = ( centerX - originX0 ) * zoomX // (center in ~world coordinate system) //originX = (centerX / zoomX) + originX - (centerX / newZoomX); //originY = (centerY / zoomY) + originY - (centerY / newZoomY); // center in image coordinate system origin.x = centerX - (centerX - origin.x) * (newZoomX / zoom.x); origin.y = centerY - (centerY - origin.y) * (newZoomY / zoom.y); // save zoom zoom.x = newZoomX; zoom.y = newZoomY; }; /** * Set the layer translation. * Translation is according to the last one. * @method setTranslate * @param {Number} tx The translation in the X direction. * @param {Number} ty The translation in the Y direction. */ this.translate = function(tx,ty) { // new origin origin.x += tx * zoom.x; origin.y += ty * zoom.y; }; /** * Set the image data array. * @method setImageData * @param {Array} data The data array. */ this.setImageData = function(data) { imageData = data; // update the cached canvas cacheCanvas.getContext("2d").putImageData(imageData, 0, 0); }; /** * Reset the layout. * @method resetLayout */ this.resetLayout = function(izoom) { origin.x = 0; origin.y = 0; zoom.x = izoom; zoom.y = izoom; }; /** * Transform a display position to an index. * @method displayToIndex */ this.displayToIndex = function ( point2D ) { return {'x': (point2D.x - origin.x) / zoom.x, 'y': (point2D.y - origin.y) / zoom.y }; }; /** * Draw the content (imageData) of the layer. * The imageData variable needs to be set * @method draw */ this.draw = function () { // clear the context: reset the transform first // store the current transformation matrix context.save(); // use the identity matrix while clearing the canvas context.setTransform( 1, 0, 0, 1, 0, 0 ); context.clearRect( 0, 0, canvas.width, canvas.height ); // restore the transform context.restore(); // draw the cached canvas on the context // transform takes as input a, b, c, d, e, f to create // the transform matrix (column-major order): // [ a c e ] // [ b d f ] // [ 0 0 1 ] context.setTransform( zoom.x, 0, 0, zoom.y, origin.x, origin.y ); context.drawImage( cacheCanvas, 0, 0 ); }; /** * Initialise the layer: set the canvas and context * @method initialise * @input {Number} inputWidth The width of the canvas. * @input {Number} inputHeight The height of the canvas. */ this.initialise = function(inputWidth, inputHeight) { // find the canvas element canvas = document.getElementById(name); if (!canvas) { alert("Error: cannot find the canvas element for '" + name + "'."); return; } // check that the getContext method exists if (!canvas.getContext) { alert("Error: no canvas.getContext method for '" + name + "'."); return; } // get the 2D context context = canvas.getContext('2d'); if (!context) { alert("Error: failed to get the 2D context for '" + name + "'."); return; } // canvas sizes canvas.width = inputWidth; canvas.height = inputHeight; // original empty image data array context.clearRect (0, 0, canvas.width, canvas.height); imageData = context.getImageData(0, 0, canvas.width, canvas.height); // cached canvas cacheCanvas = document.createElement("canvas"); cacheCanvas.width = inputWidth; cacheCanvas.height = inputHeight; }; /** * Fill the full context with the current style. * @method fillContext */ this.fillContext = function() { context.fillRect( 0, 0, canvas.width, canvas.height ); }; /** * Clear the context and reset the image data. * @method clear */ this.clear = function() { context.clearRect(0, 0, canvas.width, canvas.height); imageData = context.getImageData(0, 0, canvas.width, canvas.height); this.resetLayout(); }; /** * Merge two layers. * @method merge * @input {Layer} layerToMerge The layer to merge. It will also be emptied. */ this.merge = function(layerToMerge) { // basic resampling of the merge data to put it at zoom 1:1 var mergeImageData = layerToMerge.getContext().getImageData( 0, 0, canvas.width, canvas.height); var offMerge = 0; var offMergeJ = 0; var offThis = 0; var offThisJ = 0; var alpha = 0; for( var j=0; j < canvas.height; ++j ) { offMergeJ = parseInt( (origin.y + j * zoom.y), 10 ) * canvas.width; offThisJ = j * canvas.width; for( var i=0; i < canvas.width; ++i ) { // 4 component data: RGB + alpha offMerge = 4 * ( parseInt( (origin.x + i * zoom.x), 10 ) + offMergeJ ); offThis = 4 * ( i + offThisJ ); // merge non transparent alpha = mergeImageData.data[offMerge+3]; if( alpha !== 0 ) { imageData.data[offThis] = mergeImageData.data[offMerge]; imageData.data[offThis+1] = mergeImageData.data[offMerge+1]; imageData.data[offThis+2] = mergeImageData.data[offMerge+2]; imageData.data[offThis+3] = alpha; } } } // empty and reset merged layer layerToMerge.clear(); // draw the layer this.draw(); }; /** * Set the line color for the layer. * @method setLineColor * @input {String} color The line color. */ this.setLineColor = function(color) { context.fillStyle = color; context.strokeStyle = color; }; /** * Display the layer. * @method setStyleDisplay * @input {Boolean} val Whether to display the layer or not. */ this.setStyleDisplay = function(val) { if( val === true ) { canvas.style.display = ''; } else { canvas.style.display = "none"; } }; /** * Check if the layer is visible. * @method isVisible * @return {Boolean} True if the layer is visible. */ this.isVisible = function() { if( canvas.style.display === "none" ) { return false; } else { return true; } }; /** * Align on another layer. * @method align * @param {Layer} rhs The layer to align on. */ this.align = function(rhs) { canvas.style.top = rhs.getCanvas().offsetTop; canvas.style.left = rhs.getCanvas().offsetLeft; }; }; // Layer class /** * Get the offset of an input event. * @method getEventOffset * @static * @param {Object} event The event to get the offset from. * @return {Array} The array of offsets. */ dwv.html.getEventOffset = function (event) { var positions = []; var ex = 0; var ey = 0; if ( event.targetTouches ) { var touch = null; for ( var i = 0 ; i < event.targetTouches.length; ++i ) { touch = event.targetTouches[i]; ex = touch.pageX - app.getImageLayer().getOffset().left; ey = touch.pageY - app.getImageLayer().getOffset().top; positions.push({'x': ex, 'y': ey}); } } else { // layerX is used by Firefox ex = event.offsetX === undefined ? event.layerX : event.offsetX; ey = event.offsetY === undefined ? event.layerY : event.offsetY; positions.push({'x': ex, 'y': ey}); } return positions; };