1 /*
  2     Copyright 2008-2012
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 
 25 */
 26 
 27 /**
 28  * @fileoverview In this file the geometry element Image is defined.
 29  */
 30 
 31 /**
 32  * Construct and handle images
 33  * @class Image:
 34  * It inherits from @see GeometryElement.
 35  * @constructor
 36  */
 37 JXG.Image = function (board, url, coords, size, attributes) {
 38     this.constructor(board, attributes, JXG.OBJECT_TYPE_IMAGE, JXG.OBJECT_CLASS_OTHER);
 39 
 40     this.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER, coords, this.board);  // Still needed?
 41 
 42     if (!JXG.isFunction(coords[0]) && !JXG.isFunction(coords[1])) {
 43         this.isDraggable = true;
 44     }
 45     this.X = JXG.createFunction(coords[0],this.board,'');
 46     this.Y = JXG.createFunction(coords[1],this.board,'');
 47     this.Z = JXG.createFunction(1.0,this.board,'');
 48     this.W = JXG.createFunction(size[0],this.board,'');
 49     this.H = JXG.createFunction(size[1],this.board,'');
 50     this.coords = new JXG.Coords(JXG.COORDS_BY_USER, [this.X(),this.Y()], this.board);
 51     this.usrSize = [this.W(), this.H()];
 52     this.size = [Math.abs(this.usrSize[0]*board.unitX),Math.abs(this.usrSize[1]*board.unitY)];
 53     this.url = url;
 54     
 55     this.elType = 'image';
 56     
 57     // span contains the anchor point and the two vectors
 58     // spanning the image rectangle.
 59     this.span = [[this.Z(), this.X(), this.Y()], 
 60                   [this.Z(), this.W(), 0], 
 61                   [this.Z(), 0, this.H()]];
 62 
 63     this.parent = JXG.getRef(attributes.anchor);
 64 
 65     this.id = this.board.setId(this, 'Im');
 66 
 67     this.board.renderer.drawImage(this);
 68     if(!this.visProp.visible) {
 69        this.board.renderer.hide(this);
 70     }
 71     
 72 };
 73 
 74 JXG.Image.prototype = new JXG.GeometryElement;
 75 
 76 JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ {
 77 
 78     /**
 79      * Checks whether (x,y) is over or near the image;
 80      * @param {int} x Coordinate in x direction, screen coordinates.
 81      * @param {int} y Coordinate in y direction, screen coordinates.
 82      * @return {Boolean} True if (x,y) is over the image, False otherwise.
 83      */
 84     hasPoint: function (x,y) {
 85         var len = this.transformations.length,
 86             dx, dy, r, // No transformations
 87             c, v, p, dot;  // Transformed image
 88 
 89         // Easy case: no transformation
 90         if (len==0) {
 91             dx = x-this.coords.scrCoords[1];
 92             dy = this.coords.scrCoords[2]-y;
 93             r = this.board.options.precision.hasPoint;
 94             if (dx>=-r && dx-this.size[0]<=r && 
 95                 dy>=-r && dy-this.size[1]<=r) {
 96                     
 97                 return true;
 98             } else {
 99                 return false;
100             }
101         } else {
102             // Image is transformed
103             c = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board);
104             // v is the vector from anchor poit to the drag point
105             c = c.usrCoords;
106             v = [c[0]-this.span[0][0], 
107                  c[1]-this.span[0][1], 
108                  c[2]-this.span[0][2]];
109             dot = JXG.Math.innerProduct;   // shortcut
110             
111             // Project the drag point to the sides.
112             p = dot(v, this.span[1]);
113             if ( 0.0<=p && p <= dot(this.span[1], this.span[1])) {
114                 p = dot(v, this.span[2]);
115                 if ( 0.0<=p && p <= dot(this.span[2], this.span[2])) {
116                     return true;
117                 }
118             }
119             return false;
120         }
121     },
122 
123     /**
124      * Recalculate the coordinates of lower left corner and the width amd the height.
125      * @private
126      */
127     update: function () {
128         if (this.needsUpdate) {
129             this.updateCoords();
130             this.usrSize = [this.W(), this.H()];
131             this.size = [Math.abs(this.usrSize[0]*this.board.unitX),Math.abs(this.usrSize[1]*this.board.unitY)];
132             this.updateTransform();
133             this.updateSpan();
134         }
135         return this;
136     },
137 
138     /**
139      * Send an update request to the renderer.
140      */
141     updateRenderer: function () {
142         if (this.needsUpdate) {
143             this.board.renderer.updateImage(this);
144             this.needsUpdate = false;
145         }
146         return this;
147     },
148 
149     updateTransform: function () {
150         var i, len = this.transformations.length;
151         if (len>0) {
152             for (i=0; i<len; i++) {
153                 this.transformations[i].update();
154             }
155         }
156         return this;
157     },
158 
159     /**
160      * Updates the coordinates of the top left corner of the image.
161      */
162     updateCoords: function () {
163         this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.X(), this.Y()]);
164     },
165 
166     /**
167      * Updates the size of the image.
168      */
169     updateSize: function () {
170         this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.W(), this.H()]);
171     },
172     
173     /**
174      * Update the anchor point of the image, i.e. the lower left corner
175      * and the two vectors which span the rectangle.
176      */
177     updateSpan: function () {
178         var i, j, len = this.transformations.length, v = [];
179 
180         if (len==0) {
181             this.span = [[this.Z(), this.X(), this.Y()], 
182                           [this.Z(), this.W(), 0], 
183                           [this.Z(), 0, this.H()]];
184         } else {
185             // v contains the three defining corners of the rectangle/image
186             v[0] = [this.Z(), this.X(), this.Y()];
187             v[1] = [this.Z(), this.X()+this.W(), this.Y()];
188             v[2] = [this.Z(), this.X(), this.Y()+this.H()];
189             // Transform the three corners
190             for (i=0; i<len; i++) {
191                 for (j=0; j<3; j++) {
192                     v[j] = JXG.Math.matVecMult(this.transformations[i].matrix, v[j]);
193                 }
194             }
195             // Normalize the vectors
196             for (j=0; j<3; j++) {
197                 v[j][1] /= v[j][0];
198                 v[j][2] /= v[j][0];
199                 v[j][0] /= v[j][0];
200             }
201             // Compute the two vectors spanning the rectangle
202             // by subtracting the anchor point.
203             for (j=1; j<3; j++) {
204                 v[j][0] -= v[0][0];
205                 v[j][1] -= v[0][1];
206                 v[j][2] -= v[0][2];
207             }
208             this.span = v;
209         }
210         return this;
211     },
212 
213     addTransform: function (transform) {
214         if (JXG.isArray(transform)) {
215             for (var i=0;i<transform.length;i++) {
216                 this.transformations.push(transform[i]);
217             }
218         } else {
219             this.transformations.push(transform);
220         }
221     },
222     
223     /**
224      * Sets x and y coordinate of the image.
225      * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
226      * @param {Array} coords coordinates in screen/user units of the mouse/touch position
227      * @param {Array} oldcoords coordinates in screen/user units of the previous mouse/touch position
228      * @returns {JXG.Image}
229      */
230     setPositionDirectly: function (method, coords, oldcoords) {
231         var c = new JXG.Coords(method, coords, this.board),
232             oldc = new JXG.Coords(method, oldcoords, this.board),
233             dc,  v = [this.Z(), this.X(), this.Y()];
234             
235         dc = JXG.Math.Statistics.subtract(c.usrCoords, oldc.usrCoords);
236 
237         this.X = JXG.createFunction(v[1]+dc[1], this.board, '');
238         this.Y = JXG.createFunction(v[2]+dc[2], this.board, '');
239 
240         return this;
241     }
242     
243 });
244 
245 /**
246  * @class Displays an image. 
247  * @pseudo
248  * @description Shows an image. The image can be supplied as an URL or an base64 encoded inline image 
249  * like "data:image/png;base64, /9j/4AAQSkZJRgA..." or a function returning an URL: function(){ return 'xxx.png; }.
250  * @constructor
251  * @name Image
252  * @type JXG.Image
253  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
254  * @param {String_Array_Array} url, [position of the top left vertice], [width,height] 
255  * @example
256  * var im = board.create('image', ['http://geonext.uni-bayreuth.de/fileadmin/geonext/design/images/logo.gif', [-3,1],[5,5]]);
257  *
258  * </pre><div id="9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div>
259  * <script type="text/javascript">
260  *   var image_board = JXG.JSXGraph.initBoard('9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false});
261  *   var image_im = image_board.create('image', ['http://jsxgraph.uni-bayreuth.de/distrib/images/uccellino.jpg', [-3,1],[5,5]]);
262  * </script><pre>
263  */
264 JXG.createImage = function(board, parents, attributes) {
265     var url, attr, im;
266     attr = JXG.copyAttributes(attributes, board.options, 'image');
267     im = new JXG.Image(board, parents[0], parents[1], parents[2], attr);
268     
269     if (JXG.evaluate(attr.rotate) != 0) {
270         im.addRotation(JXG.evaluate(attr.rotate));
271     }
272     
273     return im;
274 };
275 
276 JXG.JSXGraph.registerElement('image', JXG.createImage);
277