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