View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.video.processing.effects;
31  
32  import java.util.LinkedList;
33  
34  import org.openimaj.image.FImage;
35  import org.openimaj.image.MBFImage;
36  import org.openimaj.image.processing.convolution.FGaussianConvolve;
37  import org.openimaj.image.processing.convolution.FImageConvolveSeparable;
38  import org.openimaj.image.processing.resize.ResizeProcessor;
39  import org.openimaj.video.Video;
40  import org.openimaj.video.processor.VideoProcessor;
41  
42  /**
43   * {@link VideoProcessor} that produces a slit-scan effect based on the time-map
44   * in a greyscale image.
45   *
46   * 	@author Sina Samangooei (ss@ecs.soton.ac.uk)
47   *	@author David Dupplaw (dpd@ecs.soton.ac.uk)
48   */
49  public class GreyscaleSlitScanProcessor extends VideoProcessor<MBFImage>
50  {
51  	/** The list of previous images */
52  	private final LinkedList<MBFImage> cache = new LinkedList<MBFImage>();
53  
54  	/** The kernel to blur with */
55  	private final float[] blurKern = FGaussianConvolve.makeKernel( 0.5f );
56  
57  	/** The number of images in the cache -> more = slower */
58  	private int cacheSize = 240;
59  
60  	/** The timemap image */
61  	private FImage timemapImage = null;
62  
63  	/** First time through we'll always fix the timemap */
64  	private boolean needToFixTimemap = true;
65  
66  	/**
67  	 * 	Default constructor for using the video processor in an ad-hoc manner. Uses a default
68  	 * 	cache size of 240 steps.
69  	 *
70  	 * 	@param timemap The time map image
71  	 */
72  	public GreyscaleSlitScanProcessor( final FImage timemap )
73  	{
74  		this( timemap, 240 );
75  	}
76  
77  	/**
78  	 * Default constructor for using the video processor in an ad-hoc manner.
79  	 *
80  	 * @param timemap The time map image
81  	 * @param cacheSize The number of frames to retain for creating the slitscan
82  	 *            effect
83  	 */
84  	public GreyscaleSlitScanProcessor( final FImage timemap, final int cacheSize )
85  	{
86  		this.cacheSize = cacheSize;
87  		this.timemapImage = timemap;
88  	}
89  
90  	/**
91  	 * Constructor for creating a video processor which is chainable.
92  	 *
93  	 * @param video The video to process
94  	 * @param timemap the time map
95  	 * @param cacheSize The number of frames to retain for creating the slitscan
96  	 *            effect
97  	 */
98  	public GreyscaleSlitScanProcessor( final Video<MBFImage> video, final FImage timemap, final int cacheSize )
99  	{
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 }