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 }