diff --git a/src/main/java/bdv/BigDataViewer.java b/src/main/java/bdv/BigDataViewer.java
index 9811dd32dd2cfe2bbb4f8a15dbf0c4a8ca3d2205..521dd7a22893285a6474b01b0d54e3d0edeb2be0 100644
--- a/src/main/java/bdv/BigDataViewer.java
+++ b/src/main/java/bdv/BigDataViewer.java
@@ -42,6 +42,7 @@ import bdv.spimdata.WrapBasicImgLoader;
 import bdv.spimdata.XmlIoSpimDataMinimal;
 import bdv.tools.HelpDialog;
 import bdv.tools.InitializeViewerState;
+import bdv.tools.RecordMaxProjectionDialog;
 import bdv.tools.RecordMovieDialog;
 import bdv.tools.VisibilityAndGroupingDialog;
 import bdv.tools.bookmarks.Bookmarks;
@@ -79,6 +80,8 @@ public class BigDataViewer
 
 	protected final RecordMovieDialog movieDialog;
 
+	protected final RecordMaxProjectionDialog movieMaxProjectDialog;
+
 	protected final VisibilityAndGroupingDialog activeSourcesDialog;
 
 	protected final HelpDialog helpDialog;
@@ -339,6 +342,10 @@ public class BigDataViewer
 		// this is just to get updates of window size:
 		viewer.getDisplay().addOverlayRenderer( movieDialog );
 
+		movieMaxProjectDialog = new RecordMaxProjectionDialog( viewerFrame, viewer, progressWriter );
+		// this is just to get updates of window size:
+		viewer.getDisplay().addOverlayRenderer( movieMaxProjectDialog );
+
 		activeSourcesDialog = new VisibilityAndGroupingDialog( viewerFrame, viewer.getVisibilityAndGrouping() );
 
 		helpDialog = new HelpDialog( viewerFrame );
@@ -413,6 +420,10 @@ public class BigDataViewer
 		miMovie.setText( "Record Movie" );
 		menu.add( miMovie );
 
+		final JMenuItem miMaxProjectMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_MAX_PROJECTION_MOVIE ) );
+		miMaxProjectMovie.setText( "Record Max-Projection Movie" );
+		menu.add( miMaxProjectMovie );
+
 		final JMenuItem miManualTransform = new JMenuItem( actionMap.get( BigDataViewerActions.MANUAL_TRANSFORM ) );
 		miManualTransform.setText( "Manual Transform" );
 		menu.add( miManualTransform );
diff --git a/src/main/java/bdv/BigDataViewerActions.java b/src/main/java/bdv/BigDataViewerActions.java
index f96de3513faf1000b4b0abb040c947296cb7f71f..d7e27011406b40749771ea5c4c2f98ea58e0c3b8 100644
--- a/src/main/java/bdv/BigDataViewerActions.java
+++ b/src/main/java/bdv/BigDataViewerActions.java
@@ -22,6 +22,7 @@ public class BigDataViewerActions
 	public static final String SAVE_SETTINGS = "save settings";
 	public static final String LOAD_SETTINGS = "load settings";
 	public static final String RECORD_MOVIE = "record movie";
+	public static final String RECORD_MAX_PROJECTION_MOVIE = "record max projection movie";
 	public static final String SET_BOOKMARK = "set bookmark";
 	public static final String GO_TO_BOOKMARK = "go to bookmark";
 	public static final String GO_TO_BOOKMARK_ROTATION = "go to bookmark rotation";
@@ -55,6 +56,7 @@ public class BigDataViewerActions
 		map.put( VISIBILITY_AND_GROUPING, "F6" );
 		map.put( MANUAL_TRANSFORM, "T" );
 		map.put( SHOW_HELP, "F1", "H" );
+		map.put( RECORD_MAX_PROJECTION_MOVIE, "F8" );
 		map.put( CROP, "F9" );
 		map.put( RECORD_MOVIE, "F10" );
 		map.put( SAVE_SETTINGS, "F11" );
@@ -74,6 +76,7 @@ public class BigDataViewerActions
 		map.put( new ToggleDialogAction( BRIGHTNESS_SETTINGS, bdv.brightnessDialog ) );
 		map.put( new ToggleDialogAction( VISIBILITY_AND_GROUPING, bdv.activeSourcesDialog ) );
 		map.put( new ToggleDialogAction( CROP, bdv.cropDialog ) );
+		map.put( new ToggleDialogAction( RECORD_MAX_PROJECTION_MOVIE, bdv.movieMaxProjectDialog ) );
 		map.put( new ToggleDialogAction( RECORD_MOVIE, bdv.movieDialog ) );
 		map.put( new ToggleDialogAction( SHOW_HELP, bdv.helpDialog ) );
 		map.put( new ManualTransformAction( bdv ) );
diff --git a/src/main/java/bdv/tools/RecordMaxProjectionDialog.java b/src/main/java/bdv/tools/RecordMaxProjectionDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..77aa574f4f69ed375887ec5792b6f639a9fedcf2
--- /dev/null
+++ b/src/main/java/bdv/tools/RecordMaxProjectionDialog.java
@@ -0,0 +1,378 @@
+package bdv.tools;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.BoxLayout;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.WindowConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.imglib2.Cursor;
+import net.imglib2.display.screenimage.awt.ARGBScreenImage;
+import net.imglib2.img.Img;
+import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.ui.OverlayRenderer;
+import net.imglib2.ui.PainterThread;
+import net.imglib2.ui.RenderTarget;
+import net.imglib2.util.LinAlgHelpers;
+import bdv.export.ProgressWriter;
+import bdv.img.cache.Cache;
+import bdv.util.Prefs;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.overlay.ScaleBarOverlayRenderer;
+import bdv.viewer.render.MultiResolutionRenderer;
+import bdv.viewer.state.ViewerState;
+
+public class RecordMaxProjectionDialog extends JDialog implements OverlayRenderer
+{
+	private static final long serialVersionUID = 1L;
+
+	private final ViewerPanel viewer;
+
+	private final int maxTimepoint;
+
+	private final ProgressWriter progressWriter;
+
+	private final JTextField pathTextField;
+
+	private final JSpinner spinnerMinTimepoint;
+
+	private final JSpinner spinnerMaxTimepoint;
+
+	private final JSpinner spinnerWidth;
+
+	private final JSpinner spinnerHeight;
+
+	private final JSpinner spinnerStepSize;
+
+	private final JSpinner spinnerNumSteps;
+
+	public RecordMaxProjectionDialog( final Frame owner, final ViewerPanel viewer, final ProgressWriter progressWriter )
+	{
+		super( owner, "record max projection movie", false );
+		this.viewer = viewer;
+		maxTimepoint = viewer.getState().getNumTimePoints() - 1;
+		this.progressWriter = progressWriter;
+
+		final JPanel boxes = new JPanel();
+		getContentPane().add( boxes, BorderLayout.NORTH );
+		boxes.setLayout( new BoxLayout( boxes, BoxLayout.PAGE_AXIS ) );
+
+		final JPanel saveAsPanel = new JPanel();
+		saveAsPanel.setLayout( new BorderLayout( 0, 0 ) );
+		boxes.add( saveAsPanel );
+
+		saveAsPanel.add( new JLabel( "save to" ), BorderLayout.WEST );
+
+		pathTextField = new JTextField( "./record/" );
+		saveAsPanel.add( pathTextField, BorderLayout.CENTER );
+		pathTextField.setColumns( 20 );
+
+		final JButton browseButton = new JButton( "Browse" );
+		saveAsPanel.add( browseButton, BorderLayout.EAST );
+
+		final JPanel timepointsPanel = new JPanel();
+		boxes.add( timepointsPanel );
+
+		timepointsPanel.add( new JLabel( "timepoints from" ) );
+
+		spinnerMinTimepoint = new JSpinner();
+		spinnerMinTimepoint.setModel( new SpinnerNumberModel( 0, 0, maxTimepoint, 1 ) );
+		timepointsPanel.add( spinnerMinTimepoint );
+
+		timepointsPanel.add( new JLabel( "to" ) );
+
+		spinnerMaxTimepoint = new JSpinner();
+		spinnerMaxTimepoint.setModel( new SpinnerNumberModel( maxTimepoint, 0, maxTimepoint, 1 ) );
+		timepointsPanel.add( spinnerMaxTimepoint );
+
+		final JPanel widthPanel = new JPanel();
+		boxes.add( widthPanel );
+		widthPanel.add( new JLabel( "width" ) );
+		spinnerWidth = new JSpinner();
+		spinnerWidth.setModel( new SpinnerNumberModel( 800, 10, 5000, 1 ) );
+		widthPanel.add( spinnerWidth );
+
+		final JPanel heightPanel = new JPanel();
+		boxes.add( heightPanel );
+		heightPanel.add( new JLabel( "height" ) );
+		spinnerHeight = new JSpinner();
+		spinnerHeight.setModel( new SpinnerNumberModel( 600, 10, 5000, 1 ) );
+		heightPanel.add( spinnerHeight );
+
+		final JPanel stepSizePanel = new JPanel();
+		boxes.add( stepSizePanel );
+		stepSizePanel.add( new JLabel( "slice step size" ) );
+		spinnerStepSize = new JSpinner();
+		spinnerStepSize.setModel( new SpinnerNumberModel( 1, 0.001, 20, 0.1 ) );
+		stepSizePanel.add( spinnerStepSize );
+
+		final JPanel numStepsPanel = new JPanel();
+		boxes.add( numStepsPanel );
+		numStepsPanel.add( new JLabel( "number of slices" ) );
+		spinnerNumSteps = new JSpinner();
+		spinnerNumSteps.setModel( new SpinnerNumberModel( 10, 1, 10000, 1 ) );
+		numStepsPanel.add( spinnerNumSteps );
+
+		final JPanel buttonsPanel = new JPanel();
+		boxes.add( buttonsPanel );
+		buttonsPanel.setLayout(new BorderLayout(0, 0));
+
+		final JButton recordButton = new JButton( "Record" );
+		buttonsPanel.add( recordButton, BorderLayout.EAST );
+
+		spinnerMinTimepoint.addChangeListener( new ChangeListener()
+		{
+			@Override
+			public void stateChanged( final ChangeEvent e )
+			{
+				final int min = ( Integer ) spinnerMinTimepoint.getValue();
+				final int max = ( Integer ) spinnerMaxTimepoint.getValue();
+				if ( max < min )
+					spinnerMaxTimepoint.setValue( min );
+			}
+		} );
+
+		spinnerMaxTimepoint.addChangeListener( new ChangeListener()
+		{
+			@Override
+			public void stateChanged( final ChangeEvent e )
+			{
+				final int min = ( Integer ) spinnerMinTimepoint.getValue();
+				final int max = ( Integer ) spinnerMaxTimepoint.getValue();
+				if (min > max)
+					spinnerMinTimepoint.setValue( max );
+			}
+		} );
+
+		final JFileChooser fileChooser = new JFileChooser();
+		fileChooser.setMultiSelectionEnabled( false );
+		fileChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
+
+		browseButton.addActionListener( new ActionListener()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				fileChooser.setSelectedFile( new File( pathTextField.getText() ) );
+				final int returnVal = fileChooser.showSaveDialog( null );
+				if ( returnVal == JFileChooser.APPROVE_OPTION )
+				{
+					final File file = fileChooser.getSelectedFile();
+					pathTextField.setText( file.getAbsolutePath() );
+				}
+			}
+		} );
+
+		recordButton.addActionListener( new ActionListener()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				final String dirname = pathTextField.getText();
+				final File dir = new File( dirname );
+				if ( !dir.exists() )
+					dir.mkdirs();
+				if ( !dir.exists() || !dir.isDirectory() )
+				{
+					System.err.println( "Invalid export directory " + dirname );
+					return;
+				}
+				final int minTimepointIndex = ( Integer ) spinnerMinTimepoint.getValue();
+				final int maxTimepointIndex = ( Integer ) spinnerMaxTimepoint.getValue();
+				final int width = ( Integer ) spinnerWidth.getValue();
+				final int height = ( Integer ) spinnerHeight.getValue();
+				final double stepSize = ( Double ) spinnerStepSize.getValue();
+				final int numSteps = ( Integer ) spinnerNumSteps.getValue();
+				new Thread()
+				{
+					@Override
+					public void run()
+					{
+						try
+						{
+							recordButton.setEnabled( false );
+							recordMovie( width, height, minTimepointIndex, maxTimepointIndex, stepSize, numSteps, dir );
+							recordButton.setEnabled( true );
+						}
+						catch ( final Exception ex )
+						{
+							ex.printStackTrace();
+						}
+					}
+				}.start();
+			}
+		} );
+
+		final ActionMap am = getRootPane().getActionMap();
+		final InputMap im = getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
+		final Object hideKey = new Object();
+		final Action hideAction = new AbstractAction()
+		{
+			@Override
+			public void actionPerformed( final ActionEvent e )
+			{
+				setVisible( false );
+			}
+
+			private static final long serialVersionUID = 1L;
+		};
+		im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey );
+		am.put( hideKey, hideAction );
+
+		pack();
+		setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
+	}
+
+	/**
+	 * @param stepSize in multiples of width of a source voxel.
+	 */
+	public void recordMovie( final int width, final int height, final int minTimepointIndex, final int maxTimepointIndex, final double stepSize, final int numSteps, final File dir ) throws IOException
+	{
+		final ViewerState renderState = viewer.getState();
+		final int canvasW = viewer.getDisplay().getWidth();
+		final int canvasH = viewer.getDisplay().getHeight();
+
+		final AffineTransform3D tGV = new AffineTransform3D();
+		renderState.getViewerTransform( tGV );
+		tGV.set( tGV.get( 0, 3 ) - canvasW / 2, 0, 3 );
+		tGV.set( tGV.get( 1, 3 ) - canvasH / 2, 1, 3 );
+		tGV.scale( ( double ) width / canvasW );
+		tGV.set( tGV.get( 0, 3 ) + width / 2, 0, 3 );
+		tGV.set( tGV.get( 1, 3 ) + height / 2, 1, 3 );
+
+		final AffineTransform3D affine = new AffineTransform3D();
+
+		// get voxel width transformed to current viewer coordinates
+		final AffineTransform3D tSV = new AffineTransform3D();
+		renderState.getSources().get( 0 ).getSpimSource().getSourceTransform( 0, 0, tSV );
+		tSV.preConcatenate( tGV );
+		final double[] sO = new double[] { 0, 0, 0 };
+		final double[] sX = new double[] { 1, 0, 0 };
+		final double[] vO = new double[ 3 ];
+		final double[] vX = new double[ 3 ];
+		tSV.apply( sO, vO );
+		tSV.apply( sX, vX );
+		LinAlgHelpers.subtract( vO, vX, vO );
+		final double dd = LinAlgHelpers.length( vO );
+
+		final ScaleBarOverlayRenderer scalebar = Prefs.showScaleBarInMovie() ? new ScaleBarOverlayRenderer() : null;
+
+		class MyTarget implements RenderTarget
+		{
+			final ARGBScreenImage accumulated;
+
+			public MyTarget()
+			{
+				accumulated = new ARGBScreenImage( width, height );
+			}
+
+			public void clear()
+			{
+				for ( final ARGBType acc : accumulated )
+					acc.setZero();
+			}
+
+			@Override
+			public BufferedImage setBufferedImage( final BufferedImage bufferedImage )
+			{
+				final Img< ARGBType > argbs = ArrayImgs.argbs( ( ( DataBufferInt ) bufferedImage.getData().getDataBuffer() ).getData(), width, height );
+				final Cursor< ARGBType > c = argbs.cursor();
+				for ( final ARGBType acc : accumulated )
+				{
+					final int current = acc.get();
+					final int in = c.next().get();
+					acc.set( ARGBType.rgba(
+							Math.max( ARGBType.red( in ), ARGBType.red( current ) ),
+							Math.max( ARGBType.green( in ), ARGBType.green( current ) ),
+							Math.max( ARGBType.blue( in ), ARGBType.blue( current ) ),
+							Math.max( ARGBType.alpha( in ), ARGBType.alpha( current ) )	) );
+				}
+				return null;
+			}
+
+			@Override
+			public final int getWidth()
+			{
+				return width;
+			}
+
+			@Override
+			public int getHeight()
+			{
+				return height;
+			}
+		}
+		final MyTarget target = new MyTarget();
+		final MultiResolutionRenderer renderer = new MultiResolutionRenderer( target, new PainterThread( null ), new double[] { 1 }, 0, false, 1, null, false, new Cache.Dummy() );
+		progressWriter.setProgress( 0 );
+		for ( int timepoint = minTimepointIndex; timepoint <= maxTimepointIndex; ++timepoint )
+		{
+			target.clear();
+			renderState.setCurrentTimepoint( timepoint );
+
+			for ( int step = 0; step < numSteps; ++step )
+			{
+				affine.set(
+						1, 0, 0, 0,
+						0, 1, 0, 0,
+						0, 0, 1, -dd * stepSize * step );
+				affine.concatenate( tGV );
+				renderState.setViewerTransform( affine );
+				renderer.requestRepaint();
+				renderer.paint( renderState );
+			}
+
+			final BufferedImage bi = target.accumulated.image();
+
+			if ( Prefs.showScaleBarInMovie() )
+			{
+				final Graphics2D g2 = bi.createGraphics();
+				g2.setClip( 0, 0, width, height );
+				scalebar.setViewerState( renderState );
+				scalebar.paint( g2 );
+			}
+
+			ImageIO.write( bi, "png", new File( String.format( "%s/img-%03d.png", dir, timepoint ) ) );
+			progressWriter.setProgress( ( double ) (timepoint - minTimepointIndex + 1) / (maxTimepointIndex - minTimepointIndex + 1) );
+		}
+	}
+
+	@Override
+	public void drawOverlays( final Graphics g )
+	{}
+
+	@Override
+	public void setCanvasSize( final int width, final int height )
+	{
+		spinnerWidth.setValue( width );
+		spinnerHeight.setValue( height );
+	}
+}