View Javadoc

1   /**
2    * 
3    */
4   package de.desy.acop.video;
5   
6   import java.awt.Dimension;
7   import java.awt.Rectangle;
8   import java.awt.geom.Rectangle2D;
9   import java.beans.PropertyChangeListener;
10  import java.beans.PropertyChangeSupport;
11  
12  import de.desy.acop.video.displayer.ImageZoom;
13  
14  /**
15   * </code>ScaleHelper</code> provides methods for converting rectangles from image 
16   * coordinates to component (display) coordinates. It also keeps track of the image
17   * rectangle inside the component (which can be different than the component's
18   * rectangle if aspect ratio should be kept). 
19   * 
20   * @author Tilen Kusterle, Cosylab
21   *
22   */
23  public class ScaleHelper {
24  	
25  	public static final String PROPERTY_IMAGE_AOI = "imageAoi";
26  	public static final String PROPERTY_IMAGE_DIMENSION = "imageDimension";
27  	public static final String PROPERTY_COMPONENT_DIMENSION = "componentDimension";
28  	public static final String PROPERTY_ACTIVE_RECTANGLE = "activeRectangle";
29  	public static final String PROPERTY_KEEP_ASPECT_RATIO = "keepAspectRatio";
30  	public static final String PROPERTY_AOI_ZOOM = "aoiZoom";
31  	public static final String PROPERTY_IMAGE_ZOOM = "imageZoom";
32  	
33  	private PropertyChangeSupport pcSupport;
34  	
35  	private Rectangle imageAoi = null;
36  	private Dimension imageDimension = null;
37  	private Dimension componentDimension = null;
38  	private Rectangle2D activeRectangle = null;
39  	private boolean keepAspectRatio;
40  	private boolean aoiZoom;
41  	private ImageZoom imageZoom;
42  
43  	/**
44  	 * Constructs this </code>ScaleHelper</code>.
45  	 * @param keepAspectRatio is aspect ratio kept or not
46  	 */
47  	public ScaleHelper(boolean keepAspectRatio, boolean aoiZoom, ImageZoom imageZoom) {
48  		this.keepAspectRatio = keepAspectRatio;
49  		this.aoiZoom = aoiZoom;
50  		this.imageZoom = imageZoom;
51  	}
52  	
53  	/**
54  	 * Transforms a rectangle contained within the component from display coordinates
55  	 * to image coordinates.
56  	 * @param componentRect the rectangle in display coordinates to transform
57  	 * @return the transformed rectangle in image coordinates
58  	 */
59  	public synchronized Rectangle componentToImage(Rectangle2D componentRect) {
60  		if (imageAoi == null || activeRectangle == null) return null;
61  		int roiX = (int) Math.round((componentRect.getX()-activeRectangle.getX())/activeRectangle.getWidth()*imageAoi.width);
62  		int roiY = (int) Math.round((componentRect.getY()-activeRectangle.getY())/activeRectangle.getHeight()*imageAoi.height);
63  		int roiW = (int) Math.round(componentRect.getWidth()/activeRectangle.getWidth()*imageAoi.width);
64  		int roiH = (int) Math.round(componentRect.getHeight()/activeRectangle.getHeight()*imageAoi.height);
65  		return new Rectangle(roiX, roiY, roiW, roiH);
66  	}
67  	
68  	/**
69  	 * Transforms a rectangle contained within the image from image coordinates to
70  	 * display coordinates within the component.
71  	 * @param rectangle the rectangle in image coordinates to transform
72  	 * @return the transformed rectangle in display coordinates
73  	 */
74  	public synchronized Rectangle2D imageToComponent(Rectangle2D rectangle) {
75  		return internalImageToComponent(rectangle);
76  	}
77  	
78  	private Rectangle2D internalImageToComponent(Rectangle2D rectangle) {
79  		if (imageAoi == null || componentDimension == null) return null;
80  		
81  		boolean isAspectKeeped = imageZoom == ImageZoom.AUTO && keepAspectRatio;
82  		Dimension size = isAspectKeeped ? getKeepedRatioSize(new Dimension(imageDimension.width, imageDimension.height)) : getZoomedSize();
83  
84  		Rectangle rectDst = new Rectangle((componentDimension.width-size.width)/2, (componentDimension.height-size.height)/2, size.width, size.height);
85  
86  		if (aoiZoom) {
87  			if (imageZoom == ImageZoom.AUTO && keepAspectRatio) {
88  				size = getKeepedRatioSize(imageAoi.getSize());
89  				rectDst = new Rectangle((componentDimension.width-size.width)/2, (componentDimension.height-size.height)/2, size.width, size.height);
90  			}
91  
92  		} else {
93  			double wscale_full = (double) rectDst.width / (double) imageDimension.width;
94  			double hscale_full = (double) rectDst.height / (double) imageDimension.height;
95  			rectDst.width = (int) Math.round(imageAoi.width * wscale_full);
96  			rectDst.height = (int) Math.round(imageAoi.height * hscale_full);
97  			rectDst.x += ((int) (imageAoi.x * wscale_full));
98  			rectDst.y += ((int) (imageAoi.y * hscale_full));
99  		}
100 		
101 		double rh = (double)rectDst.width/(double)imageAoi.width;
102 		double rv = (double)rectDst.height/(double)imageAoi.height;
103 		
104 		double x = rectangle.getX()*rh+rectDst.x;
105 		double y = rectangle.getY()*rv+rectDst.y;
106 		double w = rectangle.getWidth()*rh;
107 		double h = rectangle.getHeight()*rv;
108 		
109 		return new Rectangle2D.Double(x, y, w, h);
110 		
111 		// ORIGINAL!
112 //		if (imageAoi == null || componentDimension == null) return null;
113 //		
114 //		double horizontal = imageAoi.width;
115 //		double vertical = imageAoi.height;
116 //		double width = componentDimension.width;
117 //		double height = componentDimension.height;
118 //		double x, y, w, h;
119 //		
120 //		double rw = horizontal/width;
121 //		double rh = vertical/height;
122 //		if (keepAspectRatio) {
123 //			if (rw > rh) {
124 //				x = rectangle.getX()/rw;
125 //				y = rectangle.getY()/rw+(height-vertical/rw)/2;
126 //				w = rectangle.getWidth()/rw;
127 //				h = rectangle.getHeight()/rw;
128 //			} else {
129 //				x = rectangle.getX()/rh+(width-horizontal/rh)/2;
130 //				y = rectangle.getY()/rh;
131 //				w = rectangle.getWidth()/rh;
132 //				h = rectangle.getHeight()/rh;
133 //			}
134 //		} else {
135 //			x = rectangle.getX()/rw;
136 //			y = rectangle.getY()/rh;
137 //			w = rectangle.getWidth()/rw;
138 //			h = rectangle.getHeight()/rh;
139 //		}
140 //		
141 //		return new Rectangle2D.Double(x, y, w, h);
142 		
143 	}
144 	
145 	/*
146 	 * Copied from ImageDisplayer.
147 	 */
148 	private Dimension getZoomedSize() {
149 		switch (imageZoom) {
150 		case AUTO:
151 			return new Dimension(componentDimension);
152 
153 		case HALF:
154 			return new Dimension(
155 					Math.round((float)imageDimension.width/2),
156 					Math.round((float)imageDimension.height/2));
157 
158 		case NORMAL:
159 			return new Dimension(imageDimension);
160 
161 		case DOUBLE:
162 			return new Dimension(imageDimension.width*2, imageDimension.height*2);
163 
164 		default:
165 			throw new IllegalStateException("unsupported image zoom: " + imageZoom);
166 		}
167 	}
168 	
169 	/*
170 	 * Copied from ImageDisplayer.
171 	 */
172 	private Dimension getKeepedRatioSize(Dimension src) {
173 		float ratio = (float) src.width / (float) src.height;
174 		int width = componentDimension.width;
175 		int height = componentDimension.height;
176 		if (width > height) {
177 			int tmp = Math.round(ratio * height);
178 			if (tmp > width)
179 				return new Dimension(width, Math.round(width / ratio));
180 			else
181 				return new Dimension(tmp, height);
182 		} else
183 			return new Dimension(width, Math.round(width / ratio));
184 	}
185 	
186 	/**
187 	 * Gets the dimension of the image.
188 	 * @return the imageDimension
189 	 */
190 	public Dimension getImageDimension() {
191 		return imageDimension;
192 	}
193 	
194 	/**
195 	 * Gets the image AOI rectangle.
196 	 * @return the imageAoi
197 	 */
198 	public Rectangle getImageAoi() {
199 		return imageAoi;
200 	}
201 
202 	/**
203 	 * Sets the image image data (dimension and AOI).
204 	 * @param imageDimension the imageDimension to set
205 	 * @param imageAoi the imageAoi to set
206 	 */
207 	public synchronized void setImageData(Dimension imageDimension, Rectangle imageAoi) {
208 		Dimension oldDim = this.imageDimension;
209 		Rectangle oldAoi = this.imageAoi;
210 		if (imageDimension == null || imageAoi == null) {
211 			imageDimension = null;
212 			imageAoi = null;
213 		}
214 		this.imageDimension = imageDimension;
215 		this.imageAoi = imageAoi;
216 		getPcSupport().firePropertyChange(PROPERTY_IMAGE_AOI, oldAoi, imageAoi);
217 		getPcSupport().firePropertyChange(PROPERTY_IMAGE_DIMENSION, oldDim, imageDimension);
218 		if (imageAoi == null) {
219 			setActiveRectangle(null);
220 			return;
221 		}
222 		setActiveRectangle(internalImageToComponent(imageAoi));
223 	}
224 
225 
226 	/**
227 	 * Gets the dimension of the component.
228 	 * @return the componentDimension
229 	 */
230 	public synchronized Dimension getComponentDimension() {
231 		return componentDimension;
232 	}
233 
234 
235 	/**
236 	 * Sets the dimension of the component.
237 	 * @param componentDimension the componentDimension to set
238 	 */
239 	public synchronized void setComponentDimesnion(Dimension componentDimension) {
240 		if (
241 				this.componentDimension != null && componentDimension != null &&
242 				this.componentDimension.width == componentDimension.width &&
243 				this.componentDimension.height == componentDimension.height) return;
244 		Dimension old = this.componentDimension;
245 		this.componentDimension = componentDimension;
246 		getPcSupport().firePropertyChange(PROPERTY_COMPONENT_DIMENSION, old, componentDimension);
247 		if (imageAoi == null) return;
248 		setActiveRectangle(internalImageToComponent(imageAoi));
249 	}
250 
251 
252 	/**
253 	 * Gets the active rectangle (the part of the component occupied by the 
254 	 * displayed image).
255 	 * @return the activeRectangle
256 	 */
257 	public synchronized Rectangle2D getActiveRectangle() {
258 		return activeRectangle;
259 	}
260 	
261 	private void setActiveRectangle(Rectangle2D activeRectangle) {
262 		Rectangle2D old = this.activeRectangle;
263 		this.activeRectangle = activeRectangle;
264 		getPcSupport().firePropertyChange(PROPERTY_ACTIVE_RECTANGLE, old, activeRectangle);
265 	}
266 
267 
268 	/**
269 	 * Gets the keep aspect ratio property.
270 	 * @return the keepAspectRatio
271 	 */
272 	public synchronized boolean isKeepAspectRatio() {
273 		return keepAspectRatio;
274 	}
275 
276 
277 	/**
278 	 * Sets the keep aspect ratio property.
279 	 * @param keepAspectRatio the keepAspectRatio to set
280 	 */
281 	public synchronized void setKeepAspectRatio(boolean keepAspectRatio) {
282 		boolean old = this.keepAspectRatio;
283 		this.keepAspectRatio = keepAspectRatio;
284 		getPcSupport().firePropertyChange(PROPERTY_KEEP_ASPECT_RATIO, old, keepAspectRatio);
285 		if (imageAoi == null) return;
286 		setActiveRectangle(internalImageToComponent(imageAoi));
287 	}
288 	
289 	/**
290 	 * Gets the AOI zoom property.
291 	 * @return the aoiZoom
292 	 */
293 	public synchronized boolean isAoiZoom() {
294 		return aoiZoom;
295 	}
296 
297 	/**
298 	 * Sets the AOI zoom property.
299 	 * @param aoiZoom the aoiZoom to set
300 	 */
301 	public synchronized void setAoiZoom(boolean aoiZoom) {
302 		boolean old = this.aoiZoom;
303 		this.aoiZoom = aoiZoom;
304 		getPcSupport().firePropertyChange(PROPERTY_AOI_ZOOM, old, aoiZoom);
305 		if (imageAoi == null) return;
306 		setActiveRectangle(internalImageToComponent(imageAoi));
307 	}
308 
309 	/**
310 	 * Gets the </code>ImageZoom</code> property.
311 	 * @return the imageZoom
312 	 */
313 	public synchronized ImageZoom getImageZoom() {
314 		return imageZoom;
315 	}
316 
317 	/**
318 	 * Sets the </code>ImageZoom</code> property.
319 	 * @param imageZoom the imageZoom to set
320 	 */
321 	public synchronized void setImageZoom(ImageZoom imageZoom) {
322 		ImageZoom old = this.imageZoom;
323 		this.imageZoom = imageZoom;
324 		getPcSupport().firePropertyChange(PROPERTY_IMAGE_ZOOM, old, imageZoom);
325 		if (imageAoi == null) return;
326 		setActiveRectangle(internalImageToComponent(imageAoi));
327 	}
328 	
329 	private PropertyChangeSupport getPcSupport() {
330 		if (pcSupport == null) {
331 			pcSupport = new PropertyChangeSupport(this);
332 		}
333 		return pcSupport;
334 	}
335 
336 	/**
337 	 * Adds a </code>PropertyChangeListener</code> for the specified propertyName.
338 	 * @param propertyName the name of the property
339 	 * @param listener the </code>PropertyChangeListener</code> to add
340 	 */
341 	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
342 		getPcSupport().addPropertyChangeListener(propertyName, listener);
343 	}
344 	
345 	/**
346 	 * Removes a </code>PropertyChangeListener</code> for the specified propertyName.
347 	 * @param propertyName the name of the property
348 	 * @param listener the </code>PropertyChangeListener</code> to remove
349 	 */
350 	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
351 		getPcSupport().removePropertyChangeListener(propertyName, listener);
352 	}
353 	
354 	/**
355 	 * Adds a </code>PropertyChangeListener</code>.
356 	 * @param listener the </code>PropertyChangeListener</code> to add
357 	 */
358 	public void addPropertyChangeListener(PropertyChangeListener listener) {
359 		getPcSupport().addPropertyChangeListener(listener);
360 	}
361 	
362 	/**
363 	 * Removes a </code>PropertyChangeListener</code>.
364 	 * @param listener the </code>PropertyChangeListener</code> to remove
365 	 */
366 	public void removePropertyChangeListener(PropertyChangeListener listener) {
367 		getPcSupport().removePropertyChangeListener(listener);
368 	}
369 	
370 }