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