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