1 /*
  2     Copyright 2008-2017
  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