      1 /*
      2     Copyright 2008-2015
      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         this.usrSize = [this.W(), this.H()];
     84         this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)];
     85         this.url = url;
     86
     87         this.elType = 'image';
     88
     89         // span contains the anchor point and the two vectors
     90         // spanning the image rectangle.
     91         this.span = [
     92             this.coords.usrCoords.slice(0),
     93             [this.coords.usrCoords[0], this.W(), 0],
     94             [this.coords.usrCoords[0], 0, this.H()]
     95         ];
     96
     97         //this.parent = board.select(attributes.anchor);
     98         this.id = this.board.setId(this, 'Im');
     99
    100         this.board.renderer.drawImage(this);
    101         this.board.finalizeAdding(this);
    102
    103         this.methodMap = JXG.deepCopy(this.methodMap, {
    104             addTransformation: 'addTransform',
    105             trans: 'addTransform'
    106         });
    107     };
    108
    109     JXG.Image.prototype = new GeometryElement();
    110     Type.copyPrototypeMethods(JXG.Image, CoordsElement, 'coordsConstructor');
    111
    112     JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ {
    113
    114         /**
    115          * Checks whether (x,y) is over or near the image;
    116          * @param {Number} x Coordinate in x direction, screen coordinates.
    117          * @param {Number} y Coordinate in y direction, screen coordinates.
    118          * @return {Boolean} True if (x,y) is over the image, False otherwise.
    119          */
    120         hasPoint: function (x, y) {
    121             var dx, dy, r,
    122                 c, v, p, dot,
    123                 len = this.transformations.length;
    124
    125             // Easy case: no transformation
    126             if (len === 0) {
    127                 dx = x - this.coords.scrCoords[1];
    128                 dy = this.coords.scrCoords[2] - y;
    129                 r = this.board.options.precision.hasPoint;
    130
    131                 return dx >= -r && dx - this.size[0] <= r &&
    132                     dy >= -r && dy - this.size[1] <= r;
    133             }
    134
    135             // Image is transformed
    136             c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
    137             // v is the vector from anchor point to the drag point
    138             c = c.usrCoords;
    139             v = [c[0] - this.span[0][0],
    140                 c[1] - this.span[0][1],
    141                 c[2] - this.span[0][2]];
    142             dot = Mat.innerProduct;   // shortcut
    143
    144             // Project the drag point to the sides.
    145             p = dot(v, this.span[1]);
    146             if (0 <= p && p <= dot(this.span[1], this.span[1])) {
    147                 p = dot(v, this.span[2]);
    148
    149                 if (0 <= p && p <= dot(this.span[2], this.span[2])) {
    150                     return true;
    151                 }
    152             }
    153             return false;
    154         },
    155
    156         /**
    157          * Recalculate the coordinates of lower left corner and the width amd the height.
    158          * @private
    159          */
    160         update: function (fromParent) {
    161             if (!this.needsUpdate) {
    162                 return this;
    163             }
    164
    165             this.updateCoords(fromParent);
    166             this.usrSize = [this.W(), this.H()];
    167             this.size = [Math.abs(this.usrSize[0] * this.board.unitX), Math.abs(this.usrSize[1] * this.board.unitY)];
    168             this.updateSpan();
    169
    170             return this;
    171         },
    172
    173         /**
    174          * Send an update request to the renderer.
    175          */
    176         updateRenderer: function () {
    177             return this.updateRendererGeneric('updateImage');
    178         },
    179
    180         /**
    181          * Updates the size of the image.
    182          */
    183         updateSize: function () {
    184             this.coords.setCoordinates(Const.COORDS_BY_USER, [this.W(), this.H()]);
    185         },
    186
    187         /**
    188          * Update the anchor point of the image, i.e. the lower left corner
    189          * and the two vectors which span the rectangle.
    190          */
    191         updateSpan: function () {
    192             var i, j, len = this.transformations.length, v = [];
    193
    194             if (len === 0) {
    195                 this.span = [[this.Z(), this.X(), this.Y()],
    196                     [this.Z(), this.W(), 0],
    197                     [this.Z(), 0, this.H()]];
    198             } else {
    199                 // v contains the three defining corners of the rectangle/image
    200                 v[0] = [this.Z(), this.X(), this.Y()];
    201                 v[1] = [this.Z(), this.X() + this.W(), this.Y()];
    202                 v[2] = [this.Z(), this.X(), this.Y() + this.H()];
    203
    204                 // Transform the three corners
    205                 for (i = 0; i < len; i++) {
    206                     for (j = 0; j < 3; j++) {
    207                         v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]);
    208                     }
    209                 }
    210                 // Normalize the vectors
    211                 for (j = 0; j < 3; j++) {
    212                     v[j][1] /= v[j][0];
    213                     v[j][2] /= v[j][0];
    214                     v[j][0] /= v[j][0];
    215                 }
    216                 // Compute the two vectors spanning the rectangle
    217                 // by subtracting the anchor point.
    218                 for (j = 1; j < 3; j++) {
    219                     v[j][0] -= v[0][0];
    220                     v[j][1] -= v[0][1];
    221                     v[j][2] -= v[0][2];
    222                 }
    223                 this.span = v;
    224             }
    225
    226             return this;
    227         },
    228
    229         addTransform: function (transform) {
    230             var i;
    231
    232             if (Type.isArray(transform)) {
    233                 for (i = 0; i < transform.length; i++) {
    234                     this.transformations.push(transform[i]);
    235                 }
    236             } else {
    237                 this.transformations.push(transform);
    238             }
    239         }
    240     });
    241
    242     /**
    243      * @class Displays an image.
    244      * @pseudo
    245      * @description
    246      * @name Image
    247      * @type JXG.Image
    248      * @augments JXG.Image
    249      * @constructor
    250      * @constructor
    251      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    252      * @param {string,function_Array_Array} url,coords,size url defines the location of the image data. The array coords contains the user coordinates
    253      * of the lower left corner of the image.
    254      *   It can consist of two or three elements of type number, a string containing a GEONE<sub>x</sub>T
    255      *   constraint, or a function which takes no parameter and returns a number. Every element determines one coordinate. If a coordinate is
    256      *   given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained
    257      *   that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string
    258      *   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
    259      *   parent elements are given they will be interpreted as homogeneous coordinates.
    260      * <p>
    261      * The array size defines the image's width and height in user coordinates.
    262      * @example
    263      * var im = board.create('image', ['http://jsxgraph.uni-bayreuth.de/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]);
    264      *
    265      * </pre><div id="9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div>
    266      * <script type="text/javascript">
    267      *   var image_board = JXG.JSXGraph.initBoard('9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: true, showcopyright: false, shownavigation: false});
    268      *   var image_im = image_board.create('image', ['http://jsxgraph.uni-bayreuth.de/distrib/images/uccellino.jpg', [-3,-2],[3,3]]);
    269      * </script><pre>
    270      */
    271     JXG.createImage = function (board, parents, attributes) {
    272         var attr, im,
    273             url = parents[0],
    274             coords = parents[1],
    275             size = parents[2];
    276
    277         attr = Type.copyAttributes(attributes, board.options, 'image');
    278         im = CoordsElement.create(JXG.Image, board, coords, attr, url, size);
    279         if (!im) {
    280             throw new Error("JSXGraph: Can't create image with parent types '" +
    281                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    282                     "\nPossible parent types: [x,y], [z,x,y], [element,transformation]");
    283         }
    284
    285         if (Type.evaluate(attr.rotate) !== 0) {
    286             im.addRotation(Type.evaluate(attr.rotate));
    287         }
    288
    289         return im;
    290     };
    291
    292     JXG.registerElement('image', JXG.createImage);
    293
    294     return {
    295         Image: JXG.Image,
    296         createImage: JXG.createImage
    297     };
    298 });
    299