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.video;
034
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.List;
038
039import org.openimaj.image.DisplayUtilities;
040import org.openimaj.image.MBFImage;
041import org.openimaj.image.colour.RGBColour;
042import org.openimaj.image.processing.resize.ResizeProcessor;
043import org.openimaj.image.renderer.MBFImageRenderer;
044import org.openimaj.image.typography.hershey.HersheyFont;
045import org.openimaj.image.typography.hershey.HersheyFontStyle;
046import org.openimaj.math.geometry.shape.Rectangle;
047import org.openimaj.time.Timecode;
048import org.openimaj.video.Video;
049import org.openimaj.video.processing.shotdetector.HistogramVideoShotDetector;
050import org.openimaj.video.processing.shotdetector.ShotBoundary;
051
052/**
053 *      Will display a video in a timeline with shot detections marked on it.
054 *
055 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
056 *  @created 3 Jul 2012
057 *      @version $Author$, $Revision$, $Date$
058 */
059public class ShotBoundaryVideoBarVisualisation extends VideoBarVisualisation
060{
061        /** */
062        private static final long serialVersionUID = 1L;
063
064        /** Shot detector */
065        private HistogramVideoShotDetector shotDetector = null;
066
067        /**
068         *      To avoid constantly resampling, we cache the resampled images against
069         *      the hash code of the original image.
070         */
071        private final HashMap<Integer, MBFImage> imageCache = new HashMap<Integer,MBFImage>();
072
073        /**
074         *      Default constructor that takes the video to visualise
075         *      @param video The video to visualise
076         */
077        public ShotBoundaryVideoBarVisualisation( final Video<MBFImage> video )
078        {
079                super( video );
080
081                /// Use a HistogramVideoShotDetector and store the key frames
082                this.shotDetector = new HistogramVideoShotDetector( video );
083                this.shotDetector.setFindKeyframes( true );
084        }
085
086        /**
087         *      {@inheritDoc}
088         *      @see org.openimaj.vis.video.VideoBarVisualisation#processFrame(org.openimaj.image.MBFImage, org.openimaj.time.Timecode)
089         */
090        @Override
091        public void processFrame( final MBFImage frame, final Timecode t )
092        {
093                // Look for shot boundaries
094                this.shotDetector.processFrame( frame );
095        }
096
097        /**
098         *      {@inheritDoc}
099         *      @see org.openimaj.vis.video.VideoBarVisualisation#update()
100         */
101        @Override
102        public void update()
103        {
104                // Set the colour of the bar
105                this.visImage.fill( this.barColour );
106
107                // Get all the shot boundaries we currently have
108                final List<ShotBoundary<MBFImage>> sbs = new ArrayList<ShotBoundary<MBFImage>>(
109                                this.shotDetector.getShotBoundaries() );
110
111                // Store the list of bounding boxes of labels
112                final List<Rectangle> timecodeLabelBounds = new ArrayList<Rectangle>();
113
114                // Now loop through the shot boundaries.
115                for( final ShotBoundary<MBFImage> sb : sbs )
116                {
117                        // Try to get the resized image that's stored for a boundary image
118                        final int hash = sb.getKeyframe().imageAtBoundary.hashCode();
119                        MBFImage img = this.imageCache.get( hash );
120
121                        // We'll cache the resized image if it's not already there
122                        if( img == null )
123                                this.imageCache.put( hash, img = sb.getKeyframe().imageAtBoundary
124                                        .process( new ResizeProcessor( 100, 100 ) ) );
125
126                        // Now draw the image into the visualisation
127                        if( img != null )
128                        {
129                                // Find the time position of a given timecode.
130                                final int x = (int)this.getTimePosition( sb.getTimecode() );
131//                              System.out.println( "Drawing image: "+sb.getTimecode()+" -> "+x );
132                                try
133                                {
134                                        // Draw the image and its timecode
135                                        final MBFImageRenderer r = this.visImage.createRenderer();
136                                        final HersheyFont f = HersheyFont.TIMES_BOLD;
137                                        final HersheyFontStyle<Float[]> fs = f.createStyle( r );
138                                        fs.setFontSize( 12 );
139                                        final String string = sb.getTimecode().toString();
140                                        final Rectangle bounds = f.getRenderer( r ).getSize( string, fs );
141                                        bounds.translate( x, img.getHeight()+bounds.height );
142
143                                        // Move the bounds until the text isn't overlapping other text
144                                        boolean overlapping = true;
145                                        while( overlapping )
146                                        {
147                                                overlapping = false;
148                                                for( final Rectangle rect : timecodeLabelBounds )
149                                                {
150                                                        if( bounds.isOverlapping( rect ) )
151                                                        {
152                                                                bounds.translate( 0, rect.height );
153                                                                overlapping = true;
154                                                                break;
155                                                        }
156                                                }
157                                        }
158
159                                        // Store the bounds of the timecode label we're above to draw
160                                        bounds.width += 8;
161                                        timecodeLabelBounds.add( bounds );
162
163                                        // Draw the thumbnail image
164                                        r.drawImage( img, x, 0 );
165
166                                        // Draw the line at which the boundary occurred
167                                        r.drawLine( x, 0, x, this.visImage.getHeight(), 2, RGBColour.BLACK );
168
169                                        // Draw a box behind the text
170                                        r.drawShapeFilled( bounds, new Float[]{0f,0f,0f,0.3f} );
171
172                                        // Draw the text
173                                        r.drawText( string, (int)bounds.x+4, (int)(bounds.y+bounds.height), f, 12, RGBColour.WHITE );
174                                }
175                                catch( final Exception e )
176                                {
177                                        e.printStackTrace();
178                                        System.out.println( "Image was: "+img );
179                                        System.out.println( "    - Size: "+img.getWidth()+"x"+img.getHeight() );
180                                        System.out.println( "    - Num Bands: "+img.numBands() );
181                                        System.out.println( "Being drawn to: "+this.visImage );
182                                        System.out.println( "    - Size: "+this.visImage.getWidth()+"x"+this.visImage.getHeight() );
183                                        System.out.println( "    - Num Bands: "+this.visImage.numBands() );
184                                        System.out.println( "    - At Position: "+x+",0 ");
185
186                                        DisplayUtilities.display( img, "img" );
187                                        DisplayUtilities.display( this.visImage, "Vis" );
188
189                                        try
190                                        {
191                                                Thread.sleep( 100000000 );
192                                        }
193                                        catch( final InterruptedException e1 )
194                                        {
195                                                e1.printStackTrace();
196                                        }
197                                }
198                        }
199                }
200
201                super.update();
202        }
203}