You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

591 lines
23 KiB
Plaintext

9 months ago
"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 <a href="http://my.opera.com/community/forums/topic.dml?id=1052572">http://my.opera.com/community/forums/topic.dml?id=1052572</a>
*
*@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 <alex@scriptoid.com>
**/
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 <a href="http://code.google.com/p/json-sans-eval/">http://code.google.com/p/json-sans-eval/</a>
*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 <a href="http://www.json.org/js.html">http://www.json.org/js.html</a>
**/
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 <a href="http://my.opera.com/community/forums/topic.dml?id=1052572">http://my.opera.com/community/forums/topic.dml?id=1052572</a>
**/
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;
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD>ͼƬƽ<C6AC><C6BD><EFBFBD><EFBFBD>ʾ
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
<g transform = "rotate(10, 160, 50)">
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="100"> <!--I think x = 0 and y = 0 by default @see http://apike.ca/prog_svg_patterns.html-->
<rect x="10" y="10" height="3" width="3" style="stroke: #006600; fill: #DDDDDD"/>
<image x="20" y="20" width="300" height="80" xlink:href="http://diagramo.com/assets/images/logo.gif" />
</svg>
</g>
@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) + '<g transform="rotate (' + angle + ', ' + this.vector[0].x + ', ' + this.vector[0].y + ')">';
INDENTATION++;
// svg += "\n" + repeat("\t", INDENTATION) + '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">';
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) + '<image x="' + imageX + '" y="' + imageY +'" width="' + imgScaledWidth + '" height="' + imgScaleHeight + '" xlink:href="' + this.getUrl() + '" />';
// INDENTATION--;
Log.groupEnd();
// svg += "\n" + repeat("\t", INDENTATION) + '</svg>';
INDENTATION--;
svg += "\n" + repeat("\t", INDENTATION) + '</g>';
}
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;
};