1 /* 2 Copyright 2008-2021 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 utils/type 43 */ 44 45 /** 46 * @fileoverview In this file the geometry element Image is defined. 47 */ 48 49 define([ 50 'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'utils/type', 'base/coordselement' 51 ], function (JXG, Const, Coords, GeometryElement, Mat, Type, CoordsElement) { 52 53 "use strict"; 54 55 /** 56 * Construct and handle images 57 * 58 * The image can be supplied as an URL or an base64 encoded inline image 59 * like "data:image/png;base64, /9j/4AAQSkZJRgA..." or a function returning 60 * an URL: function(){ return 'xxx.png; }. 61 * 62 * @class Creates a new image object. Do not use this constructor to create a image. Use {@link JXG.Board#create} with 63 * type {@link Image} instead. 64 * @augments JXG.GeometryElement 65 * @augments JXG.CoordsElement 66 * @param {string|JXG.Board} board The board the new image is drawn on. 67 * @param {Array} coordinates An array with the user coordinates of the image. 68 * @param {Object} attributes An object containing visual and - optionally - a name and an id. 69 * @param {string|function} url An URL string or a function returning an URL string. 70 * @param {Array} size Array containing width and height of the image in user coordinates. 71 * 72 */ 73 JXG.Image = function (board, coords, attributes, url, size) { 74 this.constructor(board, attributes, Const.OBJECT_TYPE_IMAGE, Const.OBJECT_CLASS_OTHER); 75 this.element = this.board.select(attributes.anchor); 76 this.coordsConstructor(coords); 77 78 this.W = Type.createFunction(size[0], this.board, ''); 79 this.H = Type.createFunction(size[1], this.board, ''); 80 81 this.usrSize = [this.W(), this.H()]; 82 83 /** 84 * Array of length two containing [width, height] of the image in pixel. 85 * @type array 86 */ 87 this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)]; 88 89 /** 90 * 'href' of the image. This might be an URL, but also a data-uri is allowed. 91 * @type string 92 */ 93 this.url = url; 94 95 this.elType = 'image'; 96 97 // span contains the anchor point and the two vectors 98 // spanning the image rectangle. 99 this.span = [ 100 this.coords.usrCoords.slice(0), 101 [this.coords.usrCoords[0], this.W(), 0], 102 [this.coords.usrCoords[0], 0, this.H()] 103 ]; 104 105 //this.parent = board.select(attributes.anchor); 106 this.id = this.board.setId(this, 'Im'); 107 108 this.board.renderer.drawImage(this); 109 this.board.finalizeAdding(this); 110 111 this.methodMap = JXG.deepCopy(this.methodMap, { 112 addTransformation: 'addTransform', 113 trans: 'addTransform' 114 }); 115 }; 116 117 JXG.Image.prototype = new GeometryElement(); 118 Type.copyPrototypeMethods(JXG.Image, CoordsElement, 'coordsConstructor'); 119 120 JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ { 121 122 /** 123 * Checks whether (x,y) is over or near the image; 124 * @param {Number} x Coordinate in x direction, screen coordinates. 125 * @param {Number} y Coordinate in y direction, screen coordinates. 126 * @returns {Boolean} True if (x,y) is over the image, False otherwise. 127 */ 128 hasPoint: function (x, y) { 129 var dx, dy, r, type, prec, 130 c, v, p, dot, 131 len = this.transformations.length; 132 133 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 134 type = this.board._inputDevice; 135 prec = Type.evaluate(this.visProp.precision[type]); 136 } else { 137 // 'inherit' 138 prec = this.board.options.precision.hasPoint; 139 } 140 141 // Easy case: no transformation 142 if (len === 0) { 143 dx = x - this.coords.scrCoords[1]; 144 dy = this.coords.scrCoords[2] - y; 145 r = prec; 146 147 return dx >= -r && dx - this.size[0] <= r && 148 dy >= -r && dy - this.size[1] <= r; 149 } 150 151 // Image is transformed 152 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 153 // v is the vector from anchor point to the drag point 154 c = c.usrCoords; 155 v = [c[0] - this.span[0][0], 156 c[1] - this.span[0][1], 157 c[2] - this.span[0][2]]; 158 dot = Mat.innerProduct; // shortcut 159 160 // Project the drag point to the sides. 161 p = dot(v, this.span[1]); 162 if (0 <= p && p <= dot(this.span[1], this.span[1])) { 163 p = dot(v, this.span[2]); 164 165 if (0 <= p && p <= dot(this.span[2], this.span[2])) { 166 return true; 167 } 168 } 169 return false; 170 }, 171 172 /** 173 * Recalculate the coordinates of lower left corner and the width and height. 174 * 175 * @returns {JXG.GeometryElement} A reference to the element 176 * @private 177 */ 178 update: function (fromParent) { 179 if (!this.needsUpdate) { 180 return this; 181 } 182 183 this.updateCoords(fromParent); 184 this.updateSize(); 185 this.updateSpan(); 186 187 return this; 188 }, 189 190 /** 191 * Send an update request to the renderer. 192 * @private 193 */ 194 updateRenderer: function () { 195 return this.updateRendererGeneric('updateImage'); 196 }, 197 198 /** 199 * Updates the internal arrays containing size of the image. 200 * @returns {JXG.GeometryElement} A reference to the element 201 * @private 202 */ 203 updateSize: function () { 204 this.usrSize = [this.W(), this.H()]; 205 this.size = [Math.abs(this.usrSize[0] * this.board.unitX), Math.abs(this.usrSize[1] * this.board.unitY)]; 206 207 return this; 208 }, 209 210 /** 211 * Update the anchor point of the image, i.e. the lower left corner 212 * and the two vectors which span the rectangle. 213 * @returns {JXG.GeometryElement} A reference to the element 214 * @private 215 * 216 */ 217 updateSpan: function () { 218 var i, j, len = this.transformations.length, v = []; 219 220 if (len === 0) { 221 this.span = [[this.Z(), this.X(), this.Y()], 222 [this.Z(), this.W(), 0], 223 [this.Z(), 0, this.H()]]; 224 } else { 225 // v contains the three defining corners of the rectangle/image 226 v[0] = [this.Z(), this.X(), this.Y()]; 227 v[1] = [this.Z(), this.X() + this.W(), this.Y()]; 228 v[2] = [this.Z(), this.X(), this.Y() + this.H()]; 229 230 // Transform the three corners 231 for (i = 0; i < len; i++) { 232 for (j = 0; j < 3; j++) { 233 v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]); 234 } 235 } 236 // Normalize the vectors 237 for (j = 0; j < 3; j++) { 238 v[j][1] /= v[j][0]; 239 v[j][2] /= v[j][0]; 240 v[j][0] /= v[j][0]; 241 } 242 // Compute the two vectors spanning the rectangle 243 // by subtracting the anchor point. 244 for (j = 1; j < 3; j++) { 245 v[j][0] -= v[0][0]; 246 v[j][1] -= v[0][1]; 247 v[j][2] -= v[0][2]; 248 } 249 this.span = v; 250 } 251 252 return this; 253 }, 254 255 addTransform: function (transform) { 256 var i; 257 258 if (Type.isArray(transform)) { 259 for (i = 0; i < transform.length; i++) { 260 this.transformations.push(transform[i]); 261 } 262 } else { 263 this.transformations.push(transform); 264 } 265 266 return this; 267 }, 268 269 // Documented in element.js 270 getParents: function () { 271 var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize]; 272 273 if (this.parents.length !== 0) { 274 p = this.parents; 275 } 276 277 return p; 278 }, 279 280 /** 281 * Set the width and height of the image. After setting a new size, 282 * board.update() or image.fullUpdate() 283 * has to be called to make the change visible. 284 * @param {number, function, string} width Number, function or string 285 * that determines the new width of the image 286 * @param {number, function, string} height Number, function or string 287 * that determines the new height of the image 288 * @returns {JXG.GeometryElement} A reference to the element 289 * 290 * @example 291 * var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 292 * [-3,-2], [3,3]]); 293 * im.setSize(4, 4); 294 * board.update(); 295 * 296 * </pre><div id="JXG8411e60c-f009-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 297 * <script type="text/javascript"> 298 * (function() { 299 * var board = JXG.JSXGraph.initBoard('JXG8411e60c-f009-11e5-b1bf-901b0e1b8723', 300 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 301 * var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); 302 * //im.setSize(4, 4); 303 * //board.update(); 304 * 305 * })(); 306 * 307 * </script><pre> 308 * 309 * @example 310 * var p0 = board.create('point', [-3, -2]), 311 * im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 312 * [function(){ return p0.X(); }, function(){ return p0.Y(); }], 313 * [3,3]]), 314 * p1 = board.create('point', [1, 2]); 315 * 316 * im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); }); 317 * board.update(); 318 * 319 * </pre><div id="JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 320 * <script type="text/javascript"> 321 * (function() { 322 * var board = JXG.JSXGraph.initBoard('JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723', 323 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 324 * var p0 = board.create('point', [-3, -2]), 325 * im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 326 * [function(){ return p0.X(); }, function(){ return p0.Y(); }], 327 * [3,3]]), 328 * p1 = board.create('point', [1, 2]); 329 * 330 * im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); }); 331 * board.update(); 332 * 333 * })(); 334 * 335 * </script><pre> 336 * 337 */ 338 setSize: function(width, height) { 339 this.W = Type.createFunction(width, this.board, ''); 340 this.H = Type.createFunction(height, this.board, ''); 341 342 // this.fullUpdate(); 343 344 return this; 345 }, 346 347 /** 348 * Returns the width of the image in user coordinates. 349 * @returns {number} width of the image in user coordinates 350 */ 351 W: function() {}, // Needed for docs, defined in constructor 352 353 /** 354 * Returns the height of the image in user coordinates. 355 * @returns {number} height of the image in user coordinates 356 */ 357 H: function() {} // Needed for docs, defined in constructor 358 359 }); 360 361 /** 362 * @class Displays an image. 363 * @pseudo 364 * @description 365 * @name Image 366 * @type JXG.Image 367 * @augments JXG.Image 368 * @constructor 369 * @constructor 370 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 371 * @param {string,function_Array_Array} url,coords,size url defines the location of the image data. The array coords contains the user coordinates 372 * of the lower left corner of the image. 373 * It can consist of two or three elements of type number, a string containing a GEONE<sub>x</sub>T 374 * constraint, or a function which takes no parameter and returns a number. Every element determines one coordinate. If a coordinate is 375 * given by a number, the number determines the initial position of a free image. If given by a string or a function that coordinate will be constrained 376 * that means the user won't be able to change the image's position directly by mouse because it will be calculated automatically depending on the string 377 * 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 378 * parent elements are given they will be interpreted as homogeneous coordinates. 379 * <p> 380 * The array size defines the image's width and height in user coordinates. 381 * @example 382 * var im = board.create('image', ['https://jsxgraph.org/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); 383 * 384 * </pre><div class="jxgbox" id="JXG9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div> 385 * <script type="text/javascript"> 386 * var image_board = JXG.JSXGraph.initBoard('JXG9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: true, showcopyright: false, shownavigation: false}); 387 * var image_im = image_board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2],[3,3]]); 388 * </script><pre> 389 */ 390 JXG.createImage = function (board, parents, attributes) { 391 var attr, im, 392 url = parents[0], 393 coords = parents[1], 394 size = parents[2]; 395 396 attr = Type.copyAttributes(attributes, board.options, 'image'); 397 im = CoordsElement.create(JXG.Image, board, coords, attr, url, size); 398 if (!im) { 399 throw new Error("JSXGraph: Can't create image with parent types '" + 400 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 401 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]"); 402 } 403 404 if (attr.rotate !== 0) { // This is the default value, i.e. no rotation 405 im.addRotation(attr.rotate); 406 } 407 408 return im; 409 }; 410 411 JXG.registerElement('image', JXG.createImage); 412 413 return { 414 Image: JXG.Image, 415 createImage: JXG.createImage 416 }; 417 }); 418