1 /* 2 Copyright 2008-2015 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 base/element 41 math/math 42 math/statistics 43 utils/type 44 */ 45 46 /** 47 * @fileoverview In this file the geometry element Image is defined. 48 */ 49 50 define([ 51 'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'math/statistics', 'utils/type', 'base/coordselement' 52 ], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Type, CoordsElement) { 53 54 "use strict"; 55 56 /** 57 * Construct and handle images 58 * The coordinates can be relative to the coordinates of an element 59 * given in {@link JXG.Options#text.anchor}. 60 * 61 * The image can be supplied as an URL or an base64 encoded inline image 62 * like "data:image/png;base64, /9j/4AAQSkZJRgA..." or a function returning 63 * an URL: function(){ return 'xxx.png; }. 64 * 65 * @class Creates a new image object. Do not use this constructor to create a image. Use {@link JXG.Board#create} with 66 * type {@link Image} instead. 67 * @augments JXG.GeometryElement 68 * @augments JXG.CoordsElement 69 * @param {string|JXG.Board} board The board the new text is drawn on. 70 * @param {Array} coordinates An array with the user coordinates of the text. 71 * @param {Object} attributes An object containing visual and - optionally - a name and an id. 72 * @param {string|function} url An URL string or a function returning an URL string. 73 * @param {Array} size Array containing width and height of the image in user coordinates. 74 * 75 */ 76 JXG.Image = function (board, coords, attributes, url, size) { 77 this.constructor(board, attributes, Const.OBJECT_TYPE_IMAGE, Const.OBJECT_CLASS_OTHER); 78 this.element = this.board.select(attributes.anchor); 79 this.coordsConstructor(coords); 80 81 this.W = Type.createFunction(size[0], this.board, ''); 82 this.H = Type.createFunction(size[1], this.board, ''); 83 this.usrSize = [this.W(), this.H()]; 84 this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)]; 85 this.url = url; 86 87 this.elType = 'image'; 88 89 // span contains the anchor point and the two vectors 90 // spanning the image rectangle. 91 this.span = [ 92 this.coords.usrCoords.slice(0), 93 [this.coords.usrCoords[0], this.W(), 0], 94 [this.coords.usrCoords[0], 0, this.H()] 95 ]; 96 97 //this.parent = board.select(attributes.anchor); 98 this.id = this.board.setId(this, 'Im'); 99 100 this.board.renderer.drawImage(this); 101 this.board.finalizeAdding(this); 102 103 this.methodMap = JXG.deepCopy(this.methodMap, { 104 addTransformation: 'addTransform', 105 trans: 'addTransform' 106 }); 107 }; 108 109 JXG.Image.prototype = new GeometryElement(); 110 Type.copyPrototypeMethods(JXG.Image, CoordsElement, 'coordsConstructor'); 111 112 JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ { 113 114 /** 115 * Checks whether (x,y) is over or near the image; 116 * @param {Number} x Coordinate in x direction, screen coordinates. 117 * @param {Number} y Coordinate in y direction, screen coordinates. 118 * @return {Boolean} True if (x,y) is over the image, False otherwise. 119 */ 120 hasPoint: function (x, y) { 121 var dx, dy, r, 122 c, v, p, dot, 123 len = this.transformations.length; 124 125 // Easy case: no transformation 126 if (len === 0) { 127 dx = x - this.coords.scrCoords[1]; 128 dy = this.coords.scrCoords[2] - y; 129 r = this.board.options.precision.hasPoint; 130 131 return dx >= -r && dx - this.size[0] <= r && 132 dy >= -r && dy - this.size[1] <= r; 133 } 134 135 // Image is transformed 136 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 137 // v is the vector from anchor point to the drag point 138 c = c.usrCoords; 139 v = [c[0] - this.span[0][0], 140 c[1] - this.span[0][1], 141 c[2] - this.span[0][2]]; 142 dot = Mat.innerProduct; // shortcut 143 144 // Project the drag point to the sides. 145 p = dot(v, this.span[1]); 146 if (0 <= p && p <= dot(this.span[1], this.span[1])) { 147 p = dot(v, this.span[2]); 148 149 if (0 <= p && p <= dot(this.span[2], this.span[2])) { 150 return true; 151 } 152 } 153 return false; 154 }, 155 156 /** 157 * Recalculate the coordinates of lower left corner and the width amd the height. 158 * @private 159 */ 160 update: function (fromParent) { 161 if (!this.needsUpdate) { 162 return this; 163 } 164 165 this.updateCoords(fromParent); 166 this.usrSize = [this.W(), this.H()]; 167 this.size = [Math.abs(this.usrSize[0] * this.board.unitX), Math.abs(this.usrSize[1] * this.board.unitY)]; 168 this.updateSpan(); 169 170 return this; 171 }, 172 173 /** 174 * Send an update request to the renderer. 175 */ 176 updateRenderer: function () { 177 return this.updateRendererGeneric('updateImage'); 178 }, 179 180 /** 181 * Updates the size of the image. 182 */ 183 updateSize: function () { 184 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.W(), this.H()]); 185 }, 186 187 /** 188 * Update the anchor point of the image, i.e. the lower left corner 189 * and the two vectors which span the rectangle. 190 */ 191 updateSpan: function () { 192 var i, j, len = this.transformations.length, v = []; 193 194 if (len === 0) { 195 this.span = [[this.Z(), this.X(), this.Y()], 196 [this.Z(), this.W(), 0], 197 [this.Z(), 0, this.H()]]; 198 } else { 199 // v contains the three defining corners of the rectangle/image 200 v[0] = [this.Z(), this.X(), this.Y()]; 201 v[1] = [this.Z(), this.X() + this.W(), this.Y()]; 202 v[2] = [this.Z(), this.X(), this.Y() + this.H()]; 203 204 // Transform the three corners 205 for (i = 0; i < len; i++) { 206 for (j = 0; j < 3; j++) { 207 v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]); 208 } 209 } 210 // Normalize the vectors 211 for (j = 0; j < 3; j++) { 212 v[j][1] /= v[j][0]; 213 v[j][2] /= v[j][0]; 214 v[j][0] /= v[j][0]; 215 } 216 // Compute the two vectors spanning the rectangle 217 // by subtracting the anchor point. 218 for (j = 1; j < 3; j++) { 219 v[j][0] -= v[0][0]; 220 v[j][1] -= v[0][1]; 221 v[j][2] -= v[0][2]; 222 } 223 this.span = v; 224 } 225 226 return this; 227 }, 228 229 addTransform: function (transform) { 230 var i; 231 232 if (Type.isArray(transform)) { 233 for (i = 0; i < transform.length; i++) { 234 this.transformations.push(transform[i]); 235 } 236 } else { 237 this.transformations.push(transform); 238 } 239 } 240 }); 241 242 /** 243 * @class Displays an image. 244 * @pseudo 245 * @description 246 * @name Image 247 * @type JXG.Image 248 * @augments JXG.Image 249 * @constructor 250 * @constructor 251 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 252 * @param {string,function_Array_Array} url,coords,size url defines the location of the image data. The array coords contains the user coordinates 253 * of the lower left corner of the image. 254 * It can consist of two or three elements of type number, a string containing a GEONE<sub>x</sub>T 255 * constraint, or a function which takes no parameter and returns a number. Every element determines one coordinate. If a coordinate is 256 * given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained 257 * that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string 258 * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such 259 * parent elements are given they will be interpreted as homogeneous coordinates. 260 * <p> 261 * The array size defines the image's width and height in user coordinates. 262 * @example 263 * var im = board.create('image', ['http://jsxgraph.uni-bayreuth.de/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); 264 * 265 * </pre><div id="9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div> 266 * <script type="text/javascript"> 267 * var image_board = JXG.JSXGraph.initBoard('9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: true, showcopyright: false, shownavigation: false}); 268 * var image_im = image_board.create('image', ['http://jsxgraph.uni-bayreuth.de/distrib/images/uccellino.jpg', [-3,-2],[3,3]]); 269 * </script><pre> 270 */ 271 JXG.createImage = function (board, parents, attributes) { 272 var attr, im, 273 url = parents[0], 274 coords = parents[1], 275 size = parents[2]; 276 277 attr = Type.copyAttributes(attributes, board.options, 'image'); 278 im = CoordsElement.create(JXG.Image, board, coords, attr, url, size); 279 if (!im) { 280 throw new Error("JSXGraph: Can't create image with parent types '" + 281 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 282 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]"); 283 } 284 285 if (Type.evaluate(attr.rotate) !== 0) { 286 im.addRotation(Type.evaluate(attr.rotate)); 287 } 288 289 return im; 290 }; 291 292 JXG.registerElement('image', JXG.createImage); 293 294 return { 295 Image: JXG.Image, 296 createImage: JXG.createImage 297 }; 298 }); 299