Source code: com/xerox/VTM/glyphs/VImage.java
1 /* FILE: VImage.java
2 * DATE OF CREATION: Jan 09 2001
3 * AUTHOR : Emmanuel Pietriga (emmanuel.pietriga@xrce.xerox.com)
4 * MODIF: Thu Jul 10 16:42:10 2003 by Emmanuel Pietriga (emmanuel@w3.org, emmanuel@claribole.net)
5 * Copyright (c) Xerox Corporation, XRCE/Contextual Computing, 2002. All Rights Reserved
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * For full terms see the file COPYING.
18 */
19
20 package com.xerox.VTM.glyphs;
21
22 import java.awt.Color;
23 import java.awt.Graphics2D;
24 import java.awt.Font;
25 import java.awt.Stroke;
26 import java.awt.BasicStroke;
27 import java.awt.Image;
28 import java.awt.geom.AffineTransform;
29 import java.lang.Math;
30 import java.util.Vector;
31 import javax.swing.*;
32 import com.xerox.VTM.engine.*;
33
34 /**
35 * Image (rectangular) - cannot be reoriented
36 * @author Emmanuel Pietriga
37 **/
38
39 public class VImage extends Glyph implements RectangularShape,Cloneable {
40
41 public static short DRAW_BORDER_NEVER=0;
42 public static short DRAW_BORDER_MOUSE_INSIDE=1;
43 public static short DRAW_BORDER_ALWAYS=2;
44
45 /**half width and height in virtual space*/
46 long vw,vh;
47 /**aspect ratio (width divided by height)*/
48 float ar;
49
50 AffineTransform at;
51
52 /**draw border policy 0=never draw border 1=draw border if cursor inside 2=always draw border*/
53 short drawBorder=DRAW_BORDER_NEVER;
54
55 ProjImage[] pc;
56
57 Image image;
58
59 boolean zoomSensitive=true;
60
61 float scaleFactor=1.0f;
62
63 float trueCoef=1.0f;
64
65 /**
66 *@param img image to be displayed
67 */
68 public VImage(Image img){
69 vx=0;
70 vy=0;
71 vz=0;
72 image=img;
73 vw=image.getWidth(null)/2;
74 vh=image.getHeight(null)/2;
75 if (vw==0 && vh==0){ar=1.0f;}
76 else {ar=(float)vw/(float)vh;}
77 computeSize();
78 orient=0;
79 setColor(Color.black); //not actually used
80 setBorderColor(Color.black);
81 }
82
83 /**
84 *@param x coordinate in virtual space
85 *@param y coordinate in virtual space
86 *@param z altitude
87 *@param img image to be displayed
88 */
89 public VImage(long x,long y,float z,Image img){
90 vx=x;
91 vy=y;
92 vz=z;
93 image=img;
94 vw=Math.round(image.getWidth(null)/2.0);
95 vh=Math.round(image.getHeight(null)/2.0);
96 if (vw==0 && vh==0){ar=1.0f;}
97 else {ar=(float)vw/(float)vh;}
98 computeSize();
99 orient=0;
100 setColor(Color.black); //not actually used
101 setBorderColor(Color.black);
102 }
103
104 /**called when glyph is created in order to create the initial set of projected coordinates wrt the number of cameras in the space
105 *@param nbCam current number of cameras in the virtual space
106 */
107 public void initCams(int nbCam){
108 pc=new ProjImage[nbCam];
109 for (int i=0;i<nbCam;i++){
110 pc[i]=new ProjImage();
111 }
112 }
113
114 /**used internally to create new projected coordinates to use with the new camera
115 *@param verifIndex camera index, just to be sure that the number of projected coordinates is consistent with the number of cameras
116 */
117 public void addCamera(int verifIndex){
118 if (pc!=null){
119 if (verifIndex==pc.length){
120 ProjImage[] ta=pc;
121 pc=new ProjImage[ta.length+1];
122 for (int i=0;i<ta.length;i++){
123 pc[i]=ta[i];
124 }
125 pc[pc.length-1]=new ProjImage();
126 }
127 else {System.err.println("VImage:Error while adding camera "+verifIndex);}
128 }
129 else {
130 if (verifIndex==0){
131 pc=new ProjImage[1];
132 pc[0]=new ProjImage();
133 }
134 else {System.err.println("VImage:Error while adding camera "+verifIndex);}
135 }
136 }
137
138 /**if a camera is removed from the virtual space, we should delete the corresponding projected coordinates, but do not modify the array it self because we do not want to change other cameras' index - just point to null*/
139 public void removeCamera(int index){
140 pc[index]=null;
141 }
142
143 /**reset prevMouseIn for projected coordinates nb i*/
144 public void resetMouseIn(int i){
145 if (pc[i]!=null){pc[i].prevMouseIn=false;}
146 }
147
148 /**get orientation*/
149 public float getOrient(){return orient;}
150
151 /**set orientation (absolute) - has no effect*/
152 public void orientTo(float angle){}
153
154 /**get size (bounding circle radius)*/
155 public float getSize(){return size;}
156
157 /**compute size (bounding circle radius)*/
158 void computeSize(){
159 size=(float)Math.sqrt(Math.pow(vw,2)+Math.pow(vh,2));
160 }
161
162 /**set absolute half width - aspect ratio is automatically maintained (height modified accordingly)*/
163 public void setWidth(long w){
164 vw=w;
165 vh=Math.round((float)vw/ar);
166 computeSize();
167 scaleFactor=(float)(size/Math.sqrt(Math.pow(image.getWidth(null)/2,2)+Math.pow(image.getHeight(null)/2,2)));
168 try{vsm.repaintNow();}catch(NullPointerException e){}
169 }
170
171 /**set absolute half height (no effect: use setImage)*/
172 public void setHeight(long h){
173 vh=h;
174 vw=Math.round(vh*ar);
175 computeSize();
176 scaleFactor=(float)(size/Math.sqrt(Math.pow(image.getWidth(null)/2,2)+Math.pow(image.getHeight(null)/2,2)));
177 try{vsm.repaintNow();}catch(NullPointerException e){}
178 }
179
180 /**get half width*/
181 public long getWidth(){return vw;}
182
183 /**get half height*/
184 public long getHeight(){return vh;}
185
186 /**set absolute size by setting bounding circle radius */
187 public void sizeTo(float radius){
188 size=radius;
189 vw=(long)Math.round((size*ar)/(Math.sqrt(Math.pow(ar,2)+1)));
190 vh=(long)Math.round((size)/(Math.sqrt(Math.pow(ar,2)+1)));
191 scaleFactor=(float)(size/Math.sqrt(Math.pow(image.getWidth(null)/2,2)+Math.pow(image.getHeight(null)/2,2)));
192 try{vsm.repaintNow();}catch(NullPointerException e){}
193 }
194
195 /**multiply bounding circle radius by factor*/
196 public void reSize(float factor){
197 size*=factor;
198 vw=(long)Math.round((size*ar)/(Math.sqrt(Math.pow(ar,2)+1)));
199 vh=(long)Math.round((size)/(Math.sqrt(Math.pow(ar,2)+1)));
200 scaleFactor=(float)(size/Math.sqrt(Math.pow(image.getWidth(null)/2,2)+Math.pow(image.getHeight(null)/2,2)));
201 try{vsm.repaintNow();}catch(NullPointerException e){}
202 }
203
204 /**set image to be displayed*/
205 public void setImage(Image i){
206 image=i;
207 vw=Math.round(image.getWidth(null)/2.0);
208 vh=Math.round(image.getHeight(null)/2.0);
209 ar=(float)vw/(float)vh;
210 computeSize();
211 try{vsm.repaintNow();}catch(NullPointerException e){}
212 }
213
214 /**get the bitmap image to be displayed*/
215 public Image getImage(){
216 return image;
217 }
218
219 /**if false, image size is not sensitive to zoom (but its size can be changed)*/
220 public void setZoomSensitive(boolean b){
221 if (zoomSensitive!=b){
222 zoomSensitive=b;
223 try{vsm.repaintNow();}catch(NullPointerException e){}
224 }
225 }
226
227 /**if false, text size is not sensitive to zoom*/
228 public boolean isZoomSensitive(){
229 return zoomSensitive;
230 }
231
232 /**
233 *@param p one of VImage.DRAW_BORDER_*
234 */
235 public void setDrawBorderPolicy(short p){
236 if (drawBorder!=p){
237 drawBorder=p;
238 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
239 }
240 }
241
242 /**used to find out if glyph completely fills the view (in which case it is not necessary to repaint objects at a lower altitude)*/
243 public boolean fillsView(long w,long h,int camIndex){
244 return false; //can contain transparent pixel (we have no way of knowing without analysing the image data -could be done when constructing the object or setting the image)
245 }
246
247 /**detects whether the given point is inside this glyph or not
248 *@param x EXPECTS PROJECTED JPanel COORDINATE
249 *@param y EXPECTS PROJECTED JPanel COORDINATE
250 */
251 public boolean coordInside(int x,int y,int camIndex){
252 if ((x>=(pc[camIndex].cx-pc[camIndex].cw)) && (x<=(pc[camIndex].cx+pc[camIndex].cw)) && (y>=(pc[camIndex].cy-pc[camIndex].ch)) && (y<=(pc[camIndex].cy+pc[camIndex].ch))){return true;}
253 else {return false;}
254 }
255
256 /**returns 1 if mouse has entered the glyph, -1 if it has exited the glyph, 0 if nothing has changed (meaning it was already inside or outside it)*/
257 public int mouseInOut(int x,int y,int camIndex){
258 if (coordInside(x,y,camIndex)){//if the mouse is inside the glyph
259 if (!pc[camIndex].prevMouseIn){//if it was not inside it last time, mouse has entered the glyph
260 pc[camIndex].prevMouseIn=true;
261 return 1;
262 }
263 else {return 0;} //if it was inside last time, nothing has changed
264 }
265 else{//if the mouse is not inside the glyph
266 if (pc[camIndex].prevMouseIn){//if it was inside it last time, mouse has exited the glyph
267 pc[camIndex].prevMouseIn=false;
268 return -1;
269 }
270 else {return 0;} //if it was not inside last time, nothing has changed
271 }
272 }
273
274 /**project shape in camera coord sys prior to actual painting*/
275 public void project(Camera c,ViewPanel v){
276 int i=c.getIndex();
277 coef=(float)(c.focal/(c.focal+c.altitude));
278 //find coordinates of object's geom center wrt to camera center and project
279 pc[i].cx=Math.round((vx-c.posx)*coef);
280 pc[i].cy=Math.round((vy-c.posy)*coef);
281 //translate in JPanel coords
282 pc[i].cx=(v.getSize().width/2)+pc[i].cx;
283 pc[i].cy=(v.getSize().height/2)-pc[i].cy;
284 //project width and height
285 if (zoomSensitive){pc[i].cw=Math.round(vw*coef);pc[i].ch=Math.round(vh*coef);}else{pc[i].cw=(int)vw;pc[i].ch=(int)vh;}
286 }
287
288 /**draw glyph
289 *@param i camera index in the virtual space
290 *@param vW view width - used to determine if contour should be drawn or not (when it is dashed and object too big)
291 *@param vH view height - used to determine if contour should be drawn or not (when it is dashed and object too big)
292 */
293 public void draw(Graphics2D g,int vW,int vH,int i,Stroke stdS,AffineTransform stdT){
294 if ((pc[i].cw>1) && (pc[i].ch>1)){
295 if (zoomSensitive){trueCoef=scaleFactor*coef;}else{trueCoef=scaleFactor;}
296 if (Math.abs(trueCoef-1.0f)<0.01f){trueCoef=1.0f;} //a threshold greater than 0.01 causes jolts when zooming-unzooming around the 1.0 scale region
297 if (trueCoef!=1.0f){
298 g.setTransform(AffineTransform.getTranslateInstance(pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch));
299 g.drawImage(image,AffineTransform.getScaleInstance(trueCoef,trueCoef),null);
300 g.setTransform(stdT);
301 if (drawBorder==1){if (pc[i].prevMouseIn){g.setColor(borderColor);g.drawRect(pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch,2*pc[i].cw-1,2*pc[i].ch-1);}}
302 else if (drawBorder==2){g.setColor(borderColor);g.drawRect(pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch,2*pc[i].cw-1,2*pc[i].ch-1);}
303 }
304 else {
305 g.drawImage(image,pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch,null);
306 if (drawBorder==1){if (pc[i].prevMouseIn){g.setColor(borderColor);g.drawRect(pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch,2*pc[i].cw-1,2*pc[i].ch-1);}}
307 else if (drawBorder==2){g.setColor(borderColor);g.drawRect(pc[i].cx-pc[i].cw,pc[i].cy-pc[i].ch,2*pc[i].cw-1,2*pc[i].ch-1);}
308 }
309 }
310 else {g.fillRect(pc[i].cx,pc[i].cy,1,1);}
311 }
312
313
314 /**returns a clone of this object (only basic information is cloned for now: shape, orientation, position, size)*/
315 public Object clone(){
316 VImage res=new VImage(vx,vy,0,image);
317 res.setWidth(vw);
318 res.setHeight(vh);
319 res.borderColor=this.borderColor;
320 res.selectedColor=this.selectedColor;
321 res.mouseInsideColor=this.mouseInsideColor;
322 res.bColor=this.bColor;
323 res.setDrawBorderPolicy(drawBorder);
324 res.setZoomSensitive(zoomSensitive);
325 return res;
326 }
327
328 }