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