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}