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 */
030package org.openimaj.video.processing.effects;
031
032import java.util.LinkedList;
033
034import org.openimaj.image.FImage;
035import org.openimaj.image.MBFImage;
036import org.openimaj.image.processing.convolution.FGaussianConvolve;
037import org.openimaj.image.processing.convolution.FImageConvolveSeparable;
038import org.openimaj.image.processing.resize.ResizeProcessor;
039import org.openimaj.video.Video;
040import org.openimaj.video.processor.VideoProcessor;
041
042/**
043 * {@link VideoProcessor} that produces a slit-scan effect based on the time-map
044 * in a greyscale image.
045 *
046 *      @author Sina Samangooei (ss@ecs.soton.ac.uk)
047 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
048 */
049public class GreyscaleSlitScanProcessor extends VideoProcessor<MBFImage>
050{
051        /** The list of previous images */
052        private final LinkedList<MBFImage> cache = new LinkedList<MBFImage>();
053
054        /** The kernel to blur with */
055        private final float[] blurKern = FGaussianConvolve.makeKernel( 0.5f );
056
057        /** The number of images in the cache -> more = slower */
058        private int cacheSize = 240;
059
060        /** The timemap image */
061        private FImage timemapImage = null;
062
063        /** First time through we'll always fix the timemap */
064        private boolean needToFixTimemap = true;
065
066        /**
067         *      Default constructor for using the video processor in an ad-hoc manner. Uses a default
068         *      cache size of 240 steps.
069         *
070         *      @param timemap The time map image
071         */
072        public GreyscaleSlitScanProcessor( final FImage timemap )
073        {
074                this( timemap, 240 );
075        }
076
077        /**
078         * Default constructor for using the video processor in an ad-hoc manner.
079         *
080         * @param timemap The time map image
081         * @param cacheSize The number of frames to retain for creating the slitscan
082         *            effect
083         */
084        public GreyscaleSlitScanProcessor( final FImage timemap, final int cacheSize )
085        {
086                this.cacheSize = cacheSize;
087                this.timemapImage = timemap;
088        }
089
090        /**
091         * Constructor for creating a video processor which is chainable.
092         *
093         * @param video The video to process
094         * @param timemap the time map
095         * @param cacheSize The number of frames to retain for creating the slitscan
096         *            effect
097         */
098        public GreyscaleSlitScanProcessor( final Video<MBFImage> video, final FImage timemap, final int cacheSize )
099        {
100                super( video );
101                this.cacheSize = cacheSize;
102                this.timemapImage = timemap;
103        }
104
105        /**
106         * Default constructor for using the video processor in an ad-hoc manner.
107         *
108         * @param cacheSize
109         *            The number of frames to retain for creating the slitscan
110         *            effect
111         */
112        public GreyscaleSlitScanProcessor( final int cacheSize )
113        {
114                this.cacheSize = cacheSize;
115        }
116
117        /**
118         * Constructor for creating a video processor which is chainable.
119         *
120         * @param video The video to process
121         * @param cacheSize The number of frames to retain for creating the slitscan
122         *            effect
123         */
124        public GreyscaleSlitScanProcessor( final Video<MBFImage> video, final int cacheSize )
125        {
126                super( video );
127                this.cacheSize = cacheSize;
128        }
129
130        @Override
131        public MBFImage processFrame( final MBFImage frame )
132        {
133                this.addToCache( frame );
134
135                if( this.timemapImage == null || this.timemapImage.getWidth() != frame.getWidth() ||
136                                this.timemapImage.getHeight() != frame.getHeight() )
137                        this.needToFixTimemap = true;
138
139                if( this.needToFixTimemap )
140                        this.fixTimemapImage( frame.getWidth(), frame.getHeight() );
141
142                final int height = frame.getHeight();
143                final int width = frame.getWidth();
144
145                for( int y = 0; y < height; y++ )
146                {
147                        for( int x = 0; x < width; x++ )
148                        {
149                                int index = (int)this.timemapImage.pixels[y][x];
150                                if( index >= this.cache.size() )
151                                        index = this.cache.size()-1;
152
153                                final MBFImage cacheImage = this.cache.get( index );
154                                frame.setPixel( x, y, cacheImage.getPixel(x,y) );
155                        }
156                }
157
158                for( final FImage f : frame.bands )
159                {
160                        FImageConvolveSeparable.convolveVertical( f, this.blurKern );
161                }
162
163                if( this.cache.size() >= this.cacheSize ) this.cache.removeLast();
164
165                return frame;
166        }
167
168        /**
169         *      Fixes the timemap image to be the same width/height as the given value.
170         *      Will generate a default one if the image is null
171         *
172         *      @param width The width to resize the timemap to
173         *      @param height The height to resize the timemap to
174         */
175        private void fixTimemapImage( final int width, final int height )
176    {
177                // Resize the timemap
178                this.timemapImage = ResizeProcessor.resample( this.timemapImage, width, height );
179
180                // Resample the colours to the number of cache sizes.
181                for( int y = 0; y < this.timemapImage.getHeight(); y++ )
182                        for( int x = 0; x < this.timemapImage.getWidth(); x++ )
183                                this.timemapImage.pixels[y][x] = (float)
184                                        (Math.floor( this.timemapImage.pixels[y][x] * this.cacheSize));
185                this.needToFixTimemap = false;
186    }
187
188        private void addToCache( final MBFImage frame )
189        {
190                final MBFImage f = frame.clone();
191                this.cache.addFirst( f );
192        }
193}