"use strict"; /* Copyright [2014] [Diagramo] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** *This class offers support to add images to Diagramo engine *It supports PNG, GIF, PNG and SVG support. *Opera (up to 11.50 at least) does not support SVG nice. @see http://my.opera.com/community/forums/topic.dml?id=1052572 * *@this {ImageFrame} *@constructor * *@param {String} url - the URL to load the image from *@param {Number} x - the x coordinates of the center of the image *@param {Number} y - the y coordinates of the center of the image *@param {Boolean} scale - true if image should be able to scan, false otherwise. *@param {Number} frameWidth - the frame width *@param {Number} frameHeight - the frame height * *@author Alex *@see /documets/specs/imageframe_specs.jpg and /documets/specs/imageframe_specs2.jpg * *TODO: Source editor - a new popup window with 3 options (url, data, upload) and upon closing set image's src **/ function ImageFrame(url, x, y, scale, frameWidth, frameHeight){ /**Keeps track if the image was loaded*/ this.loaded = false; /**We will keep the initial point (as base line) and another point just above it - similar to a vector. * * * (x, y-20) * | * | * | * * - - - * (x+20, y) * (x, y) * *So when the image is transformed we will only transform the vector and get the new angle (if needed) *from it*/ this.vector = [new Point(x,y),new Point(x,y-20), new Point(x+20, y)]; /**Keeps track if contraints are present or set (ex: frameWidth or frameHeight )*/ this.constraints = false; /**The the frame width*/ this.frameWidth = frameWidth; if(frameWidth){ this.constraints = true; } else{ //this.frameWidth = ImageFrame.DEFAULT_WIDTH; //throw "ImageFrame.js->constructor()->frameWidth not set"; } /**The the frame height*/ this.frameHeight = frameHeight; if(frameHeight){ this.constraints = true; } else{ //this.frameHeight = ImageFrame.DEFAULT_HEIGHT; //throw "ImageFrame.js->constructor()->frameHeight not set"; } /**Trigger or not the scalling of the image, after transformations*/ this.scale = scale; /**Tell if we need to keep ratio of the image. Ignored. Set to true by default*/ this.keepRatio = true; /**The URL from where the image will be loaded. * We need to keep it also as a member to be serialized * (as the Image object is not serialized :( by JSON) **/ this.url = url; /**The JavaScript Image object. It seems that Serialization for Image, with JSON, in Chrome * is a little broken so we need custom serializers * see toJSON() method * */ this.image = new Image(); this.setUrl(url); /** * TODO: remove it * Used to display visual debug information. If set on true it will display interesting stuff (for a developer) * @deprecated - use Diagramo.debug instead * */ this.debug = false; /**The style of the Image. Kinda fake/default value*/ //TODO: do we really need to keep this? this.style = new Style(); // // //this.url = ''; /**The type of the object. Used in deserialization*/ this.oType = 'ImageFrame'; } ImageFrame.DEFAULT_WIDTH = 10; ImageFrame.DEFAULT_HEIGHT = 10; /**Creates a {ImageFrame} out of JSON parsed object *@param {JSONObject} o - the JSON parsed object *@return {ImageFrame} a newly constructed ImageFrame *@author Alex Gheorghiu **/ ImageFrame.load = function(o){ var newImageFrame = new ImageFrame(); //default: newImageFrame.loaded newImageFrame.vector = Point.loadArray(o.vector); //not needed: newImage.image newImageFrame.constraints = o.constraints; newImageFrame.frameHeight = o.frameHeight; newImageFrame.frameWidth = o.frameWidth; newImageFrame.scale = o.scale; newImageFrame.keepRatio = o.keepRatio; newImageFrame.setUrl(o.url); //not needed: newImage.style newImageFrame.debug = o.debug; //not needed: newImage.type //TODO: not good to load it twice (in constructor and now again) return newImageFrame; } /* Basic methods we need to implement * -paint:void * -tranform(matrix):void * -contains(x, y):boolean * -equals(object):boolean * -toString():String * -clone():object - this is a deep clone * -getBounds():[number, number, number, number] * -near(distance):boolean - This should be used to test if a click is close to a primitive/figure * -getPoints():Array - returns an array of points, so that a figure can implement contains * -getStyleInfo():Style - returns the different styles that can be used by any shape */ ImageFrame.prototype = { constructor : ImageFrame, /** *Called by JSON.stringify() method. We need to use this as Chrome simply cease to execute the JS *whenever an Image is found. * *Note: *Also it seems that @link http://code.google.com/p/json-sans-eval/ *treat this issue fine....but not used much...so mentioned only for informal. * *Note: *"If the stringify method sees an object that contains a toJSON method, *it calls that method, and stringifies the value returned. This allows an *object to determine its own JSON representation." * *@see http://www.json.org/js.html **/ toJSON : function() { //return JSON.stringify(this, ['loaded', 'oType']); //return JSON.stringify(this); return function (anImageFrame){ return { loaded : anImageFrame.loaded, vector : anImageFrame.vector, constraints : anImageFrame.constraints, frameWidth : anImageFrame.frameWidth, frameHeight : anImageFrame.frameHeight, scale : anImageFrame.scale, keepRatio : anImageFrame.keepRatio, url : anImageFrame.url, debug : anImageFrame.debug, style : anImageFrame.style, oType : anImageFrame.oType } }(this); }, /** *This will load the image asynchronously *@param {String} url - the URL used to load image from * *Note: It does not work with SVG under Opera 10.x @link http://my.opera.com/community/forums/topic.dml?id=1052572 **/ setUrl : function(url){ Log.info("ImageFrame.setUrl() : " + url); if(url){ //trigger only if URL is set this.url = url; this.loaded = false; //we need to reacreate the Image again as IE9 will mess up the width and heigh of the image this.image = new Image(); //Read this http://www.thefutureoftheweb.com/blog/image-onload-isnt-being-called this.image.onload = function(anImageFrame){ return function(){ anImageFrame.loaded = true; Log.info("ImageFrame.setUrl(): image finally loaded! :)"); Log.info('ImageFrame.setUrl(): image width:' + anImageFrame.image.width + ' height:' + anImageFrame.image.height); //in case no frame set use image's dimensions if(anImageFrame.constraints){ //nothing, we will keep current width and height Log.info("Constrains present"); } else{ Log.info("Original image loaded. Image height: " + anImageFrame.image.height + " width: " + anImageFrame.image.width) if(!anImageFrame.constraints){ //if no constraints than load the image naturally anImageFrame.frameHeight = anImageFrame.image.height; anImageFrame.frameWidth = anImageFrame.image.width; } } //force a repain - ouch! Log.info("ImageFrame.setUrl(): force repaint"); draw(); } } (this); //attach an error handler this.image.onerror = function(anImageFrame){ return function (){ Log.error("Error with image. URL: " + anImageFrame.url + " loaded: " + anImageFrame.loaded); } } (this); //trigger loading this.image.src = url; } }, /**We do not have to wait for an image to load to display it's URL *We can return the image's URL even if the image is not fully loaded/downloaded *@return {String} - the URL of the image or empty string ('') if no image URL present **/ getUrl : function(){ if(this.image.src){ return this.image.src; } else{ return ''; } }, /**Paint the Image into the canvas context *@param {Context} context - the 2D context of the canvas **/ paint : function(context){ if(this.loaded){ context.save(); //get rotation angle var angle = Util.getAngle(this.vector[0],this.vector[1]); //make the rotation context.translate(this.vector[0].x, this.vector[0].y); context.rotate(angle); context.translate(-this.vector[0].x, -this.vector[0].y); Log.group("A paint"); Log.info("ImageFrame.paint(): frameWidth: " + this.frameWidth + " frameHeight: " + this.frameHeight); Log.info("ImageFrame.paint(): image.width: " + this.image.width + " image.height: " + this.image.height); //scale image var wRatio = this.frameWidth / this.image.width ; var vRatio = this.frameHeight / this.image.height; //use minimum ratio var ratio = Math.min(wRatio, vRatio); Log.info("ImageFrame.paint(): wRatio: " + wRatio + " vRatio: " + vRatio + " ratio: " + ratio); //find new scalled width and height var imgScaledWidth = this.image.width * ratio; var imgScaleHeight = this.image.height * ratio; //重新设置,使背景图片平铺显示 imgScaledWidth = this.frameWidth; imgScaleHeight = this.frameHeight; Log.info("ImageFrame.paint(): imgScaledWidth: " + imgScaledWidth + " imgScaleHeight: " + imgScaleHeight); //draw image context.drawImage(this.image, this.vector[0].x - imgScaledWidth / 2, this.vector[0].y - imgScaleHeight / 2, imgScaledWidth, imgScaleHeight); Log.groupEnd(); context.restore(); } }, /**Transform image *@param {Array} matrix - the 3x3 transformation matrix **/ transform : function(matrix){ //vector's dimensions before transformation var vectorWidth = Math.sqrt( Math.pow(this.vector[0].x - this.vector[1].x, 2) + Math.pow(this.vector[0].y - this.vector[1].y, 2) ); var vectorHeight = Math.sqrt( Math.pow(this.vector[0].x - this.vector[2].x, 2) + Math.pow(this.vector[0].y - this.vector[2].y, 2) ); //transform vector this.vector[0].transform(matrix); this.vector[1].transform(matrix); this.vector[2].transform(matrix); //if scale is allowed we will do it if(this.scale){ //vector's NEW dimensions var vectorWidth2 = Math.sqrt( Math.pow(this.vector[0].x - this.vector[1].x, 2) + Math.pow(this.vector[0].y - this.vector[1].y, 2) ); var vectorHeight2 = Math.sqrt( Math.pow(this.vector[0].x - this.vector[2].x, 2) + Math.pow(this.vector[0].y - this.vector[2].y, 2) ); //find the grow/shrink ratio var vRatio = vectorWidth2 / vectorWidth; var hRatio = vectorHeight2 / vectorHeight; //update the frameset this.frameHeight *= vRatio; this.frameWidth *= hRatio; //now we have constraints if(vRatio != 1 || hRatio != 1){ this.constraints = true; } } }, /** *Get the angle around the compas between the vector and North direction *@return {Number} - the angle in radians *@see /documentation/specs/angle_around_compass.jpg *@author alex@scriptoid.com **/ getAngle: function(){ return Util.getAngle(this.vector[0], this.vector[1]); }, /**Returns the bounds the ImageFrame might have if in normal space (not rotated) *We will keep it as a Polygon *@return {Polygon} - a 4 points Polygon **/ getNormalBounds:function(){ var poly = new Polygon(); if(this.frameWidth && this.frameHeight){ //until the image is loaded we do no not have dimensions set poly.addPoint(new Point(this.vector[0].x - this.frameWidth/2, this.vector[0].y - this.frameHeight/2)); poly.addPoint(new Point(this.vector[0].x + this.frameWidth/2, this.vector[0].y - this.frameHeight/2)); poly.addPoint(new Point(this.vector[0].x + this.frameWidth/2, this.vector[0].y + this.frameHeight/2)); poly.addPoint(new Point(this.vector[0].x - this.frameWidth/2, this.vector[0].y + this.frameHeight/2)); } else{ //fake/temporary bounds poly.addPoint(new Point(this.vector[0].x - ImageFrame.DEFAULT_WIDTH / 2, this.vector[0].y - ImageFrame.DEFAULT_HEIGHT / 2)); poly.addPoint(new Point(this.vector[0].x + ImageFrame.DEFAULT_WIDTH / 2, this.vector[0].y - ImageFrame.DEFAULT_HEIGHT / 2)); poly.addPoint(new Point(this.vector[0].x + ImageFrame.DEFAULT_WIDTH / 2, this.vector[0].y + ImageFrame.DEFAULT_HEIGHT / 2)); poly.addPoint(new Point(this.vector[0].x - ImageFrame.DEFAULT_WIDTH / 2, this.vector[0].y + ImageFrame.DEFAULT_HEIGHT / 2)); } return poly; }, /**Tests if the Image contains a point defined by x and y *@param {Number} x - the x coordinates of the point *@param {Number} y - the y coordinates of the point *@return {Boolean} true - if contains, false otherwise **/ contains: function(x,y){ var contains = false; if(this.loaded){ var angle = this.getAngle(); var nBounds = this.getNormalBounds(); nBounds.transform( Matrix.translationMatrix(-this.vector[0].x, -this.vector[0].y) ); nBounds.transform(Matrix.rotationMatrix(angle)); nBounds.transform(Matrix.translationMatrix(this.vector[0].x, this.vector[0].y)); try{ contains = nBounds.contains(x,y); }catch(ex){ Log.error("ImageFrame->contains(error): " + ex); } } return contains; }, /** *Returns the bounds - in transformed space - of the Image *@return {Array} - returns [minX, minY, maxX, maxY] - bounds, where all points are in the bounds. */ getBounds : function (){ var angle = Util.getAngle(this.vector[0],this.vector[1]); var nBounds = this.getNormalBounds(); //as Polygon nBounds.transform(Matrix.translationMatrix(-this.vector[0].x, -this.vector[0].y) ); nBounds.transform(Matrix.rotationMatrix(angle)); nBounds.transform(Matrix.translationMatrix(this.vector[0].x, this.vector[0].y)); return nBounds.getBounds(); }, /** *Fake method as Image does not have the concept of points (yet?!) *But we will forward to the bounds {@link Polygon} *@return {Array} - an array of {@link Polygon}s **/ getPoints : function (){ var angle = Util.getAngle(this.vector[0],this.vector[1]); var nBounds = this.getNormalBounds(); //as Polygon nBounds.transform(Matrix.translationMatrix(-this.vector[0].x, -this.vector[0].y) ); nBounds.transform(Matrix.rotationMatrix(angle)); nBounds.transform(Matrix.translationMatrix(this.vector[0].x, this.vector[0].y)); return nBounds.getPoints(); }, /**This function returns a deed clone of current {ImageFrame} *@return {ImageFrame} - the cloned ImageFrame **/ clone:function(){ //make a new object var cImg = new ImageFrame(); //copy what we can cImg.frameHeight = this.frameHeight; cImg.frameWidth = this.frameWidth; cImg.keepRatio = this.keepRatio; cImg.constraints = this.constraints; cImg.vector = Point.cloneArray(this.vector) ; //reset the URL so that the cImg.image will be loaded with new URL cImg.setUrl(this.url); return cImg; }, /** *Export the image to SVG *@return {String} the SVG representation of the Image * Example of what image should generate @example @see http://tutorials.jenkov.com/svg/g-element.html ("SVG: g element") **/ toSVG : function (){ var svg = ''; if(this.loaded){ var angle = this.getAngle() * 180 / Math.PI; // var angle = this.getAngle() * 180 / Math.PI; svg += "\n" + repeat("\t", INDENTATION) + ''; INDENTATION++; // svg += "\n" + repeat("\t", INDENTATION) + ''; Log.group("A paint"); Log.info("ImageFrame.toSVG(): frameWidth: " + this.frameWidth + " frameHeight: " + this.frameHeight); Log.info("ImageFrame.toSVG(): image.width: " + this.image.width + " image.height: " + this.image.height); //scale image var wRatio = this.frameWidth / this.image.width ; var vRatio = this.frameHeight / this.image.height; var ratio = Math.min(wRatio, vRatio); Log.info("ImageFrame.toSVG(): wRatio: " + wRatio + " vRatio: " + vRatio + " ratio: " + ratio); var imgScaledWidth = this.image.width * ratio; var imgScaleHeight = this.image.height * ratio; Log.info("ImageFrame.toSVG(): imgScaledWidth: " + imgScaledWidth + " imgScaleHeight: " + imgScaleHeight); // context.drawImage(this.image, this.vector[0].x - this.frameWidth / 2, // this.vector[0].y - this.frameHeight / 2, this.frameWidth, this.frameHeight); var imageX = this.vector[0].x - imgScaledWidth / 2; var imageY = this.vector[0].y - imgScaleHeight / 2; // INDENTATION++; svg += "\n" + repeat("\t", INDENTATION) + ''; // INDENTATION--; Log.groupEnd(); // svg += "\n" + repeat("\t", INDENTATION) + ''; INDENTATION--; svg += "\n" + repeat("\t", INDENTATION) + ''; } return svg; } } /* * "Static" method used for importing images * @param {String} url - the URL of the image. Pay attention to come from same base URL as the application * @param {Number} x - X coordinates of the figure * @param {Number} y - Y coordinates of the figure * @author Artyom, Alex * */ ImageFrame.figure_InsertedImage = function(url, x, y) { // create new figure var f = new Figure("InsertedImage"); f.style.fillStyle = FigureDefaults.fillStyle; f.style.strokeStyle = FigureDefaults.strokeStyle; // figure's part with image var ifig = new ImageFrame(url, x, y, true); f.addPrimitive(ifig); // set callback to get image's natural size and use it for new figure ifig.image.addEventListener("load", function (){ // image size var imageWidth = ifig.image.width; var imageHeight = ifig.image.height; //Text f.properties.push(new BuilderProperty('Text', 'primitives.1.str', BuilderProperty.TYPE_TEXT)); f.properties.push(new BuilderProperty('Text Size', 'primitives.1.size', BuilderProperty.TYPE_TEXT_FONT_SIZE)); f.properties.push(new BuilderProperty('Font', 'primitives.1.font', BuilderProperty.TYPE_TEXT_FONT_FAMILY)); f.properties.push(new BuilderProperty('Alignment', 'primitives.1.align', BuilderProperty.TYPE_TEXT_FONT_ALIGNMENT)); f.properties.push(new BuilderProperty('Text Underlined', 'primitives.1.underlined', BuilderProperty.TYPE_TEXT_UNDERLINED)); f.properties.push(new BuilderProperty('Text Color', 'primitives.1.style.fillStyle', BuilderProperty.TYPE_COLOR)); f.properties.push(new BuilderProperty('URL', 'url', BuilderProperty.TYPE_URL)); var t2 = new Text(FigureDefaults.textStr, x, y + imageHeight / 2 + FigureDefaults.textSize * 2, FigureDefaults.textFont, FigureDefaults.textSize); t2.style.fillStyle = FigureDefaults.textColor; f.addPrimitive(t2); ifig.frameHeight = imageHeight + FigureDefaults.textSize; //Connection Points CONNECTOR_MANAGER.connectionPointCreate(f.id, new Point(x + imageWidth / 2, y), ConnectionPoint.TYPE_FIGURE); CONNECTOR_MANAGER.connectionPointCreate(f.id, new Point(x - imageWidth / 2, y), ConnectionPoint.TYPE_FIGURE); CONNECTOR_MANAGER.connectionPointCreate(f.id, new Point(x, y - imageHeight / 2), ConnectionPoint.TYPE_FIGURE); CONNECTOR_MANAGER.connectionPointCreate(f.id, new Point(x, y + imageHeight / 2 + FigureDefaults.textSize * 3), ConnectionPoint.TYPE_FIGURE); f.finalise(); draw(); }, false); f.finalise(); return f; };