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}