1 /*
  2     Copyright 2008-2013
  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'
 52 ], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Type) {
 53 
 54     "use strict";
 55 
 56     /**
 57      * Construct and handle images
 58      * @class Image:
 59      * It inherits from @see GeometryElement.
 60      * @constructor
 61      */
 62     JXG.Image = function (board, url, coords, size, attributes) {
 63         this.constructor(board, attributes, Const.OBJECT_TYPE_IMAGE, Const.OBJECT_CLASS_OTHER);
 64 
 65         this.initialCoords = new Coords(Const.COORDS_BY_USER, coords, this.board);  // Still needed?
 66 
 67         if (!Type.isFunction(coords[0]) && !Type.isFunction(coords[1])) {
 68             this.isDraggable = true;
 69         }
 70         this.X = Type.createFunction(coords[0], this.board, '');
 71         this.Y = Type.createFunction(coords[1], this.board, '');
 72         this.Z = Type.createFunction(1, this.board, '');
 73         this.W = Type.createFunction(size[0], this.board, '');
 74         this.H = Type.createFunction(size[1], this.board, '');
 75         this.coords = new Coords(Const.COORDS_BY_USER, [this.X(), this.Y()], this.board);
 76         this.usrSize = [this.W(), this.H()];
 77         this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)];
 78         this.url = url;
 79 
 80         this.elType = 'image';
 81 
 82         // span contains the anchor point and the two vectors
 83         // spanning the image rectangle.
 84         this.span = [
 85             [this.Z(), this.X(), this.Y()],
 86             [this.Z(), this.W(), 0],
 87             [this.Z(), 0, this.H()]
 88         ];
 89 
 90         this.parent = board.select(attributes.anchor);
 91 
 92         this.id = this.board.setId(this, 'Im');
 93 
 94         this.board.renderer.drawImage(this);
 95         if (!this.visProp.visible) {
 96             this.board.renderer.hide(this);
 97         }
 98 
 99         this.methodMap = JXG.deepCopy(this.methodMap, {
100             addTransformation: 'addTransform',
101             trans: 'addTransform'
102         });
103     };
104 
105     JXG.Image.prototype = new GeometryElement();
106 
107     JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ {
108 
109         /**
110          * Checks whether (x,y) is over or near the image;
111          * @param {Number} x Coordinate in x direction, screen coordinates.
112          * @param {Number} y Coordinate in y direction, screen coordinates.
113          * @return {Boolean} True if (x,y) is over the image, False otherwise.
114          */
115         hasPoint: function (x, y) {
116             var dx, dy, r,
117                 c, v, p, dot,
118                 len = this.transformations.length;
119 
120             // Easy case: no transformation
121             if (len === 0) {
122                 dx = x - this.coords.scrCoords[1];
123                 dy = this.coords.scrCoords[2] - y;
124                 r = this.board.options.precision.hasPoint;
125 
126                 return dx >= -r && dx - this.size[0] <= r &&
127                     dy >= -r && dy - this.size[1] <= r;
128             }
129 
130             // Image is transformed
131             c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
132             // v is the vector from anchor point to the drag point
133             c = c.usrCoords;
134             v = [c[0] - this.span[0][0],
135                 c[1] - this.span[0][1],
136                 c[2] - this.span[0][2]];
137             dot = Mat.innerProduct;   // shortcut
138 
139             // Project the drag point to the sides.
140             p = dot(v, this.span[1]);
141             if (0 <= p && p <= dot(this.span[1], this.span[1])) {
142                 p = dot(v, this.span[2]);
143 
144                 if (0 <= p && p <= dot(this.span[2], this.span[2])) {
145                     return true;
146                 }
147             }
148             return false;
149         },
150 
151         /**
152          * Recalculate the coordinates of lower left corner and the width amd the height.
153          * @private
154          */
155         update: function () {
156             if (this.needsUpdate) {
157                 if (!this.visProp.frozen) {
158                     this.updateCoords();
159                 }
160                 this.usrSize = [this.W(), this.H()];
161                 this.size = [Math.abs(this.usrSize[0] * this.board.unitX), Math.abs(this.usrSize[1] * this.board.unitY)];
162                 this.updateTransform();
163                 this.updateSpan();
164             }
165             return this;
166         },
167 
168         /**
169          * Send an update request to the renderer.
170          */
171         updateRenderer: function () {
172             if (this.needsUpdate) {
173                 this.board.renderer.updateImage(this);
174                 this.needsUpdate = false;
175             }
176 
177             return this;
178         },
179 
180         updateTransform: function () {
181             var i, len = this.transformations.length;
182 
183             if (len > 0) {
184                 for (i = 0; i < len; i++) {
185                     this.transformations[i].update();
186                 }
187             }
188 
189             return this;
190         },
191 
192         /**
193          * Updates the coordinates of the top left corner of the image.
194          */
195         updateCoords: function () {
196             this.coords.setCoordinates(Const.COORDS_BY_USER, [this.X(), this.Y()]);
197         },
198 
199         /**
200          * Updates the size of the image.
201          */
202         updateSize: function () {
203             this.coords.setCoordinates(Const.COORDS_BY_USER, [this.W(), this.H()]);
204         },
205 
206         /**
207          * Update the anchor point of the image, i.e. the lower left corner
208          * and the two vectors which span the rectangle.
209          */
210         updateSpan: function () {
211             var i, j, len = this.transformations.length, v = [];
212 
213             if (len === 0) {
214                 this.span = [[this.Z(), this.X(), this.Y()],
215                     [this.Z(), this.W(), 0],
216                     [this.Z(), 0, this.H()]];
217             } else {
218                 // v contains the three defining corners of the rectangle/image
219                 v[0] = [this.Z(), this.X(), this.Y()];
220                 v[1] = [this.Z(), this.X() + this.W(), this.Y()];
221                 v[2] = [this.Z(), this.X(), this.Y() + this.H()];
222 
223                 // Transform the three corners
224                 for (i = 0; i < len; i++) {
225                     for (j = 0; j < 3; j++) {
226                         v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]);
227                     }
228                 }
229                 // Normalize the vectors
230                 for (j = 0; j < 3; j++) {
231                     v[j][1] /= v[j][0];
232                     v[j][2] /= v[j][0];
233                     v[j][0] /= v[j][0];
234                 }
235                 // Compute the two vectors spanning the rectangle
236                 // by subtracting the anchor point.
237                 for (j = 1; j < 3; j++) {
238                     v[j][0] -= v[0][0];
239                     v[j][1] -= v[0][1];
240                     v[j][2] -= v[0][2];
241                 }
242                 this.span = v;
243             }
244 
245             return this;
246         },
247 
248         addTransform: function (transform) {
249             var i;
250 
251             if (Type.isArray(transform)) {
252                 for (i = 0; i < transform.length; i++) {
253                     this.transformations.push(transform[i]);
254                 }
255             } else {
256                 this.transformations.push(transform);
257             }
258         },
259 
260         /**
261          * Sets x and y coordinate of the image.
262          * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
263          * @param {Array} coords coordinates in screen/user units of the mouse/touch position
264          * @param {Array} oldcoords coordinates in screen/user units of the previous mouse/touch position
265          * @returns {JXG.Image} this element
266          */
267         setPositionDirectly: function (method, coords, oldcoords) {
268             var dc,
269                 c = new Coords(method, coords, this.board),
270                 oldc = new Coords(method, oldcoords, this.board),
271                 v = [this.Z(), this.X(), this.Y()];
272 
273             dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
274 
275             this.X = Type.createFunction(v[1] + dc[1], this.board, '');
276             this.Y = Type.createFunction(v[2] + dc[2], this.board, '');
277 
278             return this;
279         }
280 
281     });
282 
283     /**
284      * @class Displays an image.
285      * @pseudo
286      * @description Shows an image. The image can be supplied as an URL or an base64 encoded inline image
287      * like "data:image/png;base64, /9j/4AAQSkZJRgA..." or a function returning an URL: function(){ return 'xxx.png; }.
288      * @constructor
289      * @name Image
290      * @type JXG.Image
291      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
292      * @param {String_Array_Array} url,_topleft,_widthheight url defines the location of the image data. Optional topleft and
293      * widthheight define the user coordinates of the top left corner and the image's width and height.
294      * @example
295      * var im = board.create('image', ['http://geonext.uni-bayreuth.de/fileadmin/geonext/design/images/logo.gif', [-3,1],[5,5]]);
296      *
297      * </pre><div id="9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div>
298      * <script type="text/javascript">
299      *   var image_board = JXG.JSXGraph.initBoard('9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false});
300      *   var image_im = image_board.create('image', ['http://jsxgraph.uni-bayreuth.de/distrib/images/uccellino.jpg', [-3,1],[5,5]]);
301      * </script><pre>
302      */
303     JXG.createImage = function (board, parents, attributes) {
304         var attr, im;
305 
306         attr = Type.copyAttributes(attributes, board.options, 'image');
307         im = new JXG.Image(board, parents[0], parents[1], parents[2], attr);
308 
309         if (Type.evaluate(attr.rotate) !== 0) {
310             im.addRotation(Type.evaluate(attr.rotate));
311         }
312 
313         return im;
314     };
315 
316     JXG.registerElement('image', JXG.createImage);
317 
318     return {
319         Image: JXG.Image,
320         createImage: JXG.createImage
321     };
322 });