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(this.evalVisProp('precision'))) { 143 type = this.board._inputDevice; 144 prec = this.evalVisProp('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, j, 228 len = this.transformations.length, 229 v = []; 230 231 if (len === 0) { 232 this.span = [ 233 [this.Z(), this.X(), this.Y()], 234 [this.Z(), this.W(), 0], 235 [this.Z(), 0, this.H()] 236 ]; 237 } else { 238 // v contains the three defining corners of the rectangle/image 239 v[0] = [this.Z(), this.X(), this.Y()]; 240 v[1] = [this.Z(), this.X() + this.W(), this.Y()]; 241 v[2] = [this.Z(), this.X(), this.Y() + this.H()]; 242 // Transform the three corners 243 for (i = 0; i < len; i++) { 244 for (j = 0; j < 3; j++) { 245 v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]); 246 } 247 } 248 // Normalize the vectors 249 for (j = 0; j < 3; j++) { 250 v[j][1] /= v[j][0]; 251 v[j][2] /= v[j][0]; 252 v[j][0] /= v[j][0]; 253 } 254 // Compute the two vectors spanning the rectangle 255 // by subtracting the anchor point. 256 for (j = 1; j < 3; j++) { 257 v[j][0] -= v[0][0]; 258 v[j][1] -= v[0][1]; 259 v[j][2] -= v[0][2]; 260 } 261 this.span = v; 262 } 263 264 return this; 265 }, 266 267 addTransform: function (transform) { 268 var i; 269 270 if (Type.isArray(transform)) { 271 for (i = 0; i < transform.length; i++) { 272 this.transformations.push(transform[i]); 273 } 274 } else { 275 this.transformations.push(transform); 276 } 277 278 return this; 279 }, 280 281 // Documented in element.js 282 getParents: function () { 283 var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize]; 284 285 if (this.parents.length !== 0) { 286 p = this.parents; 287 } 288 289 return p; 290 }, 291 292 /** 293 * Set the width and height of the image. After setting a new size, 294 * board.update() or image.fullUpdate() 295 * has to be called to make the change visible. 296 * @param {number|function|string} width Number, function or string 297 * that determines the new width of the image 298 * @param {number|function|string} height Number, function or string 299 * that determines the new height of the image 300 * @returns {JXG.GeometryElement} A reference to the element 301 * 302 * @example 303 * var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 304 * [-3,-2], [3,3]]); 305 * im.setSize(4, 4); 306 * board.update(); 307 * 308 * </pre><div id="JXG8411e60c-f009-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 309 * <script type="text/javascript"> 310 * (function() { 311 * var board = JXG.JSXGraph.initBoard('JXG8411e60c-f009-11e5-b1bf-901b0e1b8723', 312 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 313 * var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); 314 * //im.setSize(4, 4); 315 * //board.update(); 316 * 317 * })(); 318 * 319 * </script><pre> 320 * 321 * @example 322 * var p0 = board.create('point', [-3, -2]), 323 * im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 324 * [function(){ return p0.X(); }, function(){ return p0.Y(); }], 325 * [3,3]]), 326 * p1 = board.create('point', [1, 2]); 327 * 328 * im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); }); 329 * board.update(); 330 * 331 * </pre><div id="JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 332 * <script type="text/javascript"> 333 * (function() { 334 * var board = JXG.JSXGraph.initBoard('JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723', 335 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 336 * var p0 = board.create('point', [-3, -2]), 337 * im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', 338 * [function(){ return p0.X(); }, function(){ return p0.Y(); }], 339 * [3,3]]), 340 * p1 = board.create('point', [1, 2]); 341 * 342 * im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); }); 343 * board.update(); 344 * 345 * })(); 346 * 347 * </script><pre> 348 * 349 */ 350 setSize: function (width, height) { 351 this.W = Type.createFunction(width, this.board, ""); 352 this.H = Type.createFunction(height, this.board, ""); 353 this.addParentsFromJCFunctions([this.W, this.H]); 354 // this.fullUpdate(); 355 356 return this; 357 }, 358 359 /** 360 * Returns the width of the image in user coordinates. 361 * @returns {number} width of the image in user coordinates 362 */ 363 W: function () {}, // Needed for docs, defined in constructor 364 365 /** 366 * Returns the height of the image in user coordinates. 367 * @returns {number} height of the image in user coordinates 368 */ 369 H: function () {} // Needed for docs, defined in constructor 370 } 371 ); 372 373 /** 374 * @class Displays an image. 375 * @pseudo 376 * @name Image 377 * @type JXG.Image 378 * @augments JXG.Image 379 * @constructor 380 * @constructor 381 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 382 * @param {string,function_Array_Array} url,coords,size url defines the location of the image data. The array coords contains the user coordinates 383 * of the lower left corner of the image. 384 * It can consist of two or three elements of type number, a string containing a GEONE<sub>x</sub>T 385 * constraint, or a function which takes no parameter and returns a number. Every element determines one coordinate. If a coordinate is 386 * 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 387 * 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 388 * 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 389 * parent elements are given they will be interpreted as homogeneous coordinates. 390 * <p> 391 * The array size defines the image's width and height in user coordinates. 392 * @example 393 * var im = board.create('image', ['https://jsxgraph.org/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); 394 * 395 * </pre><div class="jxgbox" id="JXG9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div> 396 * <script type="text/javascript"> 397 * var image_board = JXG.JSXGraph.initBoard('JXG9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: true, showcopyright: false, shownavigation: false}); 398 * var image_im = image_board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2],[3,3]]); 399 * </script><pre> 400 */ 401 JXG.createImage = function (board, parents, attributes) { 402 var attr, 403 im, 404 url = parents[0], 405 coords = parents[1], 406 size = parents[2]; 407 408 attr = Type.copyAttributes(attributes, board.options, "image"); 409 im = CoordsElement.create(JXG.Image, board, coords, attr, url, size); 410 if (!im) { 411 throw new Error( 412 "JSXGraph: Can't create image with parent types '" + 413 typeof parents[0] + 414 "' and '" + 415 typeof parents[1] + 416 "'." + 417 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]" 418 ); 419 } 420 421 if (attr.rotate !== 0) { 422 // This is the default value, i.e. no rotation 423 im.addRotation(attr.rotate); 424 } 425 426 return im; 427 }; 428 429 JXG.registerElement("image", JXG.createImage); 430 431 export default JXG.Image; 432 // export default { 433 // Image: JXG.Image, 434 // createImage: JXG.createImage 435 // }; 436