001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.vis; 034 035import java.awt.Dimension; 036import java.awt.Graphics; 037import java.awt.GridLayout; 038import java.awt.event.ComponentEvent; 039import java.awt.event.ComponentListener; 040import java.awt.image.BufferedImage; 041import java.util.ArrayList; 042import java.util.List; 043 044import javax.swing.JFrame; 045import javax.swing.JPanel; 046 047import org.openimaj.image.ImageUtilities; 048import org.openimaj.image.MBFImage; 049 050/** 051 * A top level class for visualisations. This class handles the creation 052 * of an image (MBFImage) for drawing the visualisation into. It also makes 053 * the visualisation available as a Swing JPanel and, using {@link #showWindow(String)}, 054 * allows the visualisation to be displayed in a window. 055 * <p> 056 * This class is abstract - it does not know how to paint the data on which it 057 * is typed. It is therefore necessary to subclass this class with specific 058 * implementations of visualisations for specific types. To do this, override 059 * the {@link #update()} method in this class to draw the visualisation into 060 * the <code>visImage</code> member. The implementation of the {@link #update()} 061 * method may call {@link #repaint()} if it so wishes to update any displays 062 * of the data. This is called automatically when {@link #setData(Object)} is 063 * called to update the data object. 064 * <p> 065 * The class also allows for linking visualisations so that one visualisation 066 * can draw on top of another visualisation's output. However, the visualisations 067 * do not affect each other's visualisation images. If you call the constructor 068 * that takes a visualisation, this visualisation will become an overlay of 069 * the given visualisation. Each time the underlying visualisation is updated, 070 * a copy of the visualisation is stored in this visualisation for drawing as 071 * a background. This means that this visualisation can update more often than 072 * the underlying visualisation, however, it can be slow if the underlying 073 * visualisation updates often because it's making a copy of the image. 074 * 075 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 076 * @created 27 Jul 2012 077 * @version $Author$, $Revision$, $Date$ 078 * @param <T> The type of the data to be visualised 079 */ 080public abstract class VisualisationImpl<T> extends JPanel 081 implements ComponentListener, Visualisation<T>, AnimatedVisualisationListener 082{ 083 /** */ 084 private static final long serialVersionUID = 1L; 085 086 /** The visualisation image */ 087 protected MBFImage visImage = null; 088 089 /** The double buffer image */ 090 private MBFImage dbImage = null; 091 092 /** The data to be visualised */ 093 protected T data = null; 094 095 /** Whether to allow resizing of the visualisation */ 096 private boolean allowResize = true; 097 098 /** The overlay image */ 099 private MBFImage overlayImage = null; 100 101 /** The list of visualisations that wish to overlay on this vis */ 102 private final List<VisualisationImpl<?>> overlays = new ArrayList<VisualisationImpl<?>>(); 103 104 /** Whether to clear the image before redrawing */ 105 protected boolean clearBeforeDraw = true; 106 107 /** The background colour to clear the image to */ 108 private final Float[] backgroundColour = new Float[]{0f,0f,0f,1f}; 109 110 /** The visualisation that this vis is being overlaid on */ 111 private VisualisationImpl<?> overlaidOn = null; 112 113 /** The required size of the vis */ 114 private Dimension requiredSize = new Dimension( 400, 400 ); 115 116 /** 117 * Default constructor 118 */ 119 protected VisualisationImpl() {} 120 121 /** 122 * Create a new visualisation with the given width and height 123 * @param width The width 124 * @param height The height 125 */ 126 public VisualisationImpl( final int width, final int height ) 127 { 128 this.visImage = new MBFImage( width, height, 4 ); 129 this.dbImage = this.visImage.clone(); 130 this.setPreferredSize( new Dimension( width, height ) ); 131 this.setSize( new Dimension( width, height ) ); 132 this.requiredSize = new Dimension( width, height ); 133 } 134 135 /** 136 * Create a new visualisation using an existing image. 137 * @param overlayOn The visualisation on which to overlay 138 */ 139 public VisualisationImpl( final VisualisationImpl<?> overlayOn ) 140 { 141 this.overlaidOn = overlayOn; 142 143 // Create an image the same size as the overlay vis 144 final MBFImage vi = overlayOn.getVisualisationImage(); 145 this.visImage = new MBFImage( vi.getWidth(), vi.getHeight(), 4 ); 146 this.dbImage = this.visImage.clone(); 147 this.setPreferredSize( new Dimension( vi.getWidth(), vi.getHeight() ) ); 148 this.setSize( new Dimension( vi.getWidth(), vi.getHeight() ) ); 149 this.requiredSize = this.getSize(); 150 151 // Add this as an overlay on the other vis. This also forces 152 // an update so that we get their visualisation to overlay on 153 overlayOn.addOverlay( this ); 154 this.addComponentListener( this ); 155 } 156 157 /** 158 * Add an overlay to this visualisation 159 * @param v The visualisation to overlay on this visualisation 160 */ 161 public void addOverlay( final VisualisationImpl<?> v ) 162 { 163 this.overlays.add( v ); 164 v.updateVis( this.visImage ); 165 } 166 167 /** 168 * Remove the given overlay from this visualisation 169 * @param v The visualisation to remove 170 */ 171 public void removeOverlay( final VisualisationImpl<?> v ) 172 { 173 this.overlays.remove( v ); 174 } 175 176 /** 177 * Called to update the visualisation. This method can expect the 178 * <code>visImage</code> member to be available and of the correct size. 179 * The method simply needs to draw the visualisation to this {@link MBFImage}. 180 * You should wrap any drawing code in a synchronized block, synchronized on 181 * the visImage - this stops the image being repainted to the screen half way 182 * through drawing. 183 * <p> 184 * Update is called from the paint() method so should 185 * ideally not force a repaint() as this will call a continuous repaint 186 * loop. 187 */ 188 public abstract void update(); 189 190 /** 191 * Call to force and update of the visualisation 192 */ 193 @Override 194 public void updateVis() 195 { 196 if( this.visImage == null ) 197 { 198 this.visImage = new MBFImage( (int)this.requiredSize.getWidth(), (int)this.requiredSize.getHeight(), 4 ); 199 this.dbImage = this.visImage.clone(); 200 } 201 202// System.out.println( "Updating "+this ); 203 synchronized( this.visImage ) 204 { 205 if( this.allowResize && (this.visImage.getWidth() != this.requiredSize.getWidth() || 206 this.visImage.getHeight() != this.requiredSize.getHeight()) ) 207 { 208 this.visImage = new MBFImage( (int)this.requiredSize.getWidth(), (int)this.requiredSize.getHeight(), 4 ); 209 this.dbImage = this.visImage.clone(); 210 System.out.println( this.requiredSize ); 211 } 212 213 if( this.clearBeforeDraw ) 214 this.visImage.fill( this.backgroundColour ); 215 216 if( this.overlayImage != null ) 217 this.visImage.drawImage( this.overlayImage, 0, 0 ); 218 } 219 220 this.update(); 221 222 synchronized( this.visImage ) { 223 for( final VisualisationImpl<?> v : this.overlays ) 224 v.updateVis( this.visImage ); 225 226 final MBFImage t = this.dbImage; 227 this.dbImage = this.visImage; 228 this.visImage = t; 229 } 230 231 this.repaint(); 232 } 233 234 /** 235 * Update the visualisation using the given image as the base image 236 * on which to overlay. 237 * @param overlay The overlay 238 */ 239 public void updateVis( final MBFImage overlay ) 240 { 241 if( overlay != null ) 242 this.overlayImage = overlay.clone(); 243 else this.overlayImage = null; 244 this.updateVis(); 245 } 246 247 /** 248 * Set the data to be visualised. 249 * @param data The data to be visualised 250 */ 251 @Override 252 public void setData( final T data ) 253 { 254 synchronized( data ) 255 { 256 this.data = data; 257 } 258 259 this.updateVis(); 260 } 261 262 /** 263 * Returns the image to which the bars will be drawn. 264 * @return The image 265 */ 266 @Override 267 public MBFImage getVisualisationImage() 268 { 269 // The implementation of this appears to return our double 270 // buffered image. That's because at the end of the updateVis() 271 // method we swap the images over, so the last drawn image is 272 // only available in dbImage. (Any drawing that's occuring right 273 // now will be happening on visImage, so it's ok to access this 274 // here.) 275 return this.dbImage.clone(); 276 } 277 278 /** 279 * {@inheritDoc} 280 * @see javax.swing.JComponent#paint(java.awt.Graphics) 281 */ 282 @Override 283 public void paint( final Graphics g ) 284 { 285 synchronized( this.visImage ) 286 { 287 final BufferedImage bi = ImageUtilities.createBufferedImageForDisplay( this.dbImage ); 288 g.drawImage( bi, 0, 0, null ); 289 } 290 } 291 292 /** 293 * {@inheritDoc} 294 * @see javax.swing.JComponent#update(java.awt.Graphics) 295 */ 296 @Override 297 public void update( final Graphics g ) 298 { 299 this.updateVis(); 300 this.repaint(); 301 } 302 303 /** 304 * Show a window containing this visualisation 305 * @param title The title of the window 306 * @return The window that was created 307 */ 308 public JFrame showWindow( final String title ) 309 { 310 final JFrame f = new JFrame( title ); 311 f.getContentPane().setLayout( new GridLayout(1,1) ); 312 f.getContentPane().add( this ); 313 f.setResizable( this.allowResize ); 314 f.pack(); 315 f.setVisible( true ); 316 this.addComponentListener( this ); 317 this.requiredSize = this.getSize(); 318 this.updateVis(); 319 return f; 320 } 321 322 /** 323 * Whether this visualisation can be resized. 324 * @return TRUE if this visualisation can be resized. 325 */ 326 public boolean isAllowResize() 327 { 328 return this.allowResize; 329 } 330 331 /** 332 * Set whether this visualisation can be resized. 333 * @param allowResize TRUE to allow the visualisation to be resizable 334 */ 335 public void setAllowResize( final boolean allowResize ) 336 { 337 this.allowResize = allowResize; 338 } 339 340 /** 341 * Set the required size of the vis 342 * @param d The required size 343 */ 344 @Override 345 public void setRequiredSize( final Dimension d ) 346 { 347 this.requiredSize = d; 348 } 349 350 /** 351 * Get the current required size of the vis 352 * @return The required size 353 */ 354 public Dimension getRequiredSize() 355 { 356 return this.requiredSize; 357 } 358 359 /** 360 * Sets whether to clear the image before drawing. Has no effect if 361 * fade drawing is used. 362 * @param tf TRUE to clear the image 363 */ 364 public void setClearBeforeDraw( final boolean tf ) 365 { 366 this.clearBeforeDraw = tf; 367 } 368 369 @Override 370 public void componentHidden( final ComponentEvent e ) 371 { 372 } 373 374 @Override 375 public void componentMoved( final ComponentEvent e ) 376 { 377 } 378 379 @Override 380 public void componentShown( final ComponentEvent e ) 381 { 382 } 383 384 @Override 385 public void componentResized( final ComponentEvent e ) 386 { 387 if( this.getSize().width == this.visImage.getWidth() && 388 this.getSize().height == this.visImage.getHeight() ) 389 return; 390 391 this.requiredSize = this.getSize(); 392 if( this.overlaidOn != null ) 393 this.overlaidOn.setSize( this.getSize() ); 394 else this.updateVis(); 395 } 396 397 /** 398 * {@inheritDoc} 399 * @see org.openimaj.vis.AnimatedVisualisationListener#newVisualisationAvailable(org.openimaj.vis.AnimatedVisualisationProvider) 400 */ 401 @Override 402 public void newVisualisationAvailable( final AnimatedVisualisationProvider avp ) 403 { 404// avp.updateVis(); 405 this.updateVis(); 406 } 407}