diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
index d2f21d4db5bbe17238e4b0e4b8cf9c949a6a7e22..29f859a3339df7a03a265a5437edc37cdf7baf75 100644
--- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
+++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
@@ -31,9 +31,8 @@ package bdv.viewer.render;
 import bdv.cache.CacheControl;
 import bdv.util.MovingAverage;
 import bdv.viewer.ViewerState;
+import bdv.viewer.render.ScreenScales.ScreenScale;
 import java.util.concurrent.ExecutorService;
-import net.imglib2.Dimensions;
-import net.imglib2.FinalDimensions;
 import net.imglib2.Interval;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.Volatile;
@@ -44,7 +43,6 @@ import net.imglib2.ui.PainterThread;
 import net.imglib2.ui.RenderTarget;
 import net.imglib2.util.Intervals;
 
-
 /**
  * A renderer that uses a coarse-to-fine rendering scheme. First, a small target
  * image at a fraction of the canvas resolution is rendered. Then, increasingly
@@ -115,84 +113,9 @@ public class MultiResolutionRenderer
 	private final long[] iobudget = new long[] { 100l * 1000000l,  10l * 1000000l };
 
 	/**
-	 * Scale factors from the {@link #display viewer canvas} to the
-	 * {@code screenImages}.
-	 *
-	 * A scale factor of 1 means 1 pixel in the screen image is displayed as 1
-	 * pixel on the canvas, a scale factor of 0.5 means 1 pixel in the screen
-	 * image is displayed as 2 pixel on the canvas, etc.
+	 * TODO javadoc
 	 */
-	private final double[] screenScaleFactors;
-
-	static class ScreenScale
-	{
-		/**
-		 * The width of the target image at this ScreenScale.
-		 */
-		final int w;
-
-		/**
-		 * The height of the target image at this ScreenScale.
-		 */
-		final int h;
-
-		/**
-		 * The transformation from viewer to target image coordinates at this ScreenScale.
-		 */
-		final AffineTransform3D scale;
-
-		private final double screenToViewerScale;
-
-		public ScreenScale( final int screenW, final int screenH, final double screenToViewerScale )
-		{
-			this.screenToViewerScale = screenToViewerScale;
-
-			w = ( int ) Math.ceil( screenToViewerScale * screenW );
-			h = ( int ) Math.ceil( screenToViewerScale * screenH );
-			scale = new AffineTransform3D();
-			scale.set( screenToViewerScale, 0, 0 );
-			scale.set( screenToViewerScale, 1, 1 );
-			scale.set( 0.5 * screenToViewerScale - 0.5, 0, 3 );
-			scale.set( 0.5 * screenToViewerScale - 0.5, 1, 3 );
-		}
-
-		Interval requestedScreenInterval = null;
-
-		public void requestInterval( final Interval screenInterval )
-		{
-			requestedScreenInterval = requestedScreenInterval == null ? screenInterval : Intervals.union( requestedScreenInterval, screenInterval );
-		}
-
-		public Interval pullScreenInterval()
-		{
-			final Interval interval = requestedScreenInterval;
-			requestedScreenInterval = null;
-			return interval;
-		}
-
-		public double estimateIntervalRenderNanos( final double renderNanosPerPixel )
-		{
-			return renderNanosPerPixel * Intervals.numElements( scaleScreenInterval( requestedScreenInterval ) );
-		}
-
-		public Interval scaleScreenInterval( final Interval requestedScreenInterval )
-		{
-			// This is equivalent to
-			// Intervals.intersect( new FinalInterval( w, h ), Intervals.smallestContainingInterval( Intervals.scale( requestedScreenInterval, screenToViewerScale ) ) );
-			return Intervals.createMinMax(
-					Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 0 ) * screenToViewerScale ) ),
-					Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 1 ) * screenToViewerScale ) ),
-					Math.min( w - 1, ( int ) Math.ceil( requestedScreenInterval.max( 0 ) * screenToViewerScale ) ),
-					Math.min( h - 1, ( int ) Math.ceil( requestedScreenInterval.max( 1 ) * screenToViewerScale ) )
-			);
-		}
-	}
-
-	private final ScreenScale[] screenScales;
-
-	private int screenW;
-
-	private int screenH;
+	private final ScreenScales screenScales;
 
 	/**
 	 * Maintains arrays for intermediate per-source render images and masks.
@@ -321,15 +244,14 @@ public class MultiResolutionRenderer
 		this.painterThread = painterThread;
 		projector = null;
 		currentScreenScaleIndex = -1;
-		this.screenScaleFactors = screenScaleFactors.clone();
-		this.screenScales = new ScreenScale[ screenScaleFactors.length ];
+		screenScales = new ScreenScales( screenScaleFactors );
 		renderStorage = new RenderStorage();
 
 		this.targetRenderNanos = targetRenderNanos;
 		renderNanosPerPixelAndSource = new MovingAverage( 3 );
 		renderNanosPerPixelAndSource.init( 500 );
 
-		requestedScreenScaleIndex = screenScales.length - 1;
+		requestedScreenScaleIndex = screenScales.size() - 1;
 		renderingMayBeCancelled = false;
 		this.cacheControl = cacheControl;
 		newFrameRequest = false;
@@ -343,26 +265,6 @@ public class MultiResolutionRenderer
 				accumulateProjectorFactory );
 	}
 
-	/**
-	 * Check whether the size of the display component was changed and
-	 * recreate {@link #screenScales} accordingly.
-	 *
-	 * @return whether the size was changed.
-	 */
-	private boolean checkResize()
-	{
-		final int newScreenW = display.getWidth();
-		final int newScreenH = display.getHeight();
-		if ( newScreenW != screenW || newScreenH != screenH )
-		{
-			screenW = newScreenW;
-			screenH = newScreenH;
-			for ( int i = 0; i < screenScales.length; ++i )
-				screenScales[ i ] = new ScreenScale( screenW, screenH, screenScaleFactors[ i ] );
-			return true;
-		}
-		return false;
-	}
 
 	/**
 	 * Render image at the {@link #requestedScreenScaleIndex requested screen
@@ -370,10 +272,12 @@ public class MultiResolutionRenderer
 	 */
 	public boolean paint( final ViewerState viewerState )
 	{
-		if ( display.getWidth() <= 0 || display.getHeight() <= 0 )
+		int screenW = display.getWidth();
+		int screenH = display.getHeight();
+		if ( screenW <= 0 || screenH <= 0 )
 			return false;
 
-		final boolean resized = checkResize();
+		final boolean resized = screenScales.checkResize( screenW, screenH );
 
 		final boolean newFrame;
 		final boolean newInterval;
@@ -405,7 +309,8 @@ public class MultiResolutionRenderer
 			cacheControl.prepareNextFrame();
 			currentViewerState = viewerState.snapshot();
 			currentNumVisibleSources = currentViewerState.getVisibleAndPresentSources().size();
-			requestedScreenScaleIndex = suggestScreenScale( new FinalDimensions( screenW, screenH ), currentNumVisibleSources );
+			final double renderNanosPerPixel = renderNanosPerPixelAndSource.getAverage() * currentNumVisibleSources;
+			requestedScreenScaleIndex = screenScales.suggestScreenScale( renderNanosPerPixel, targetRenderNanos );
 		}
 
 		final boolean createProjector = newFrame || ( requestedScreenScaleIndex != currentScreenScaleIndex );
@@ -423,13 +328,13 @@ public class MultiResolutionRenderer
 		{
 			if ( createProjector )
 			{
-				final ScreenScale screenScale = screenScales[ requestedScreenScaleIndex ];
+				final ScreenScale screenScale = screenScales.get( requestedScreenScaleIndex );
 
 				renderResult = display.getReusableRenderResult();
-				renderResult.init( screenScale.w, screenScale.h );
-				renderResult.setScaleFactor( screenScaleFactors[ requestedScreenScaleIndex ] );
+				renderResult.init( screenScale.width(), screenScale.height() );
+				renderResult.setScaleFactor( screenScale.scale() );
 
-				renderStorage.checkRenewData( screenScales[ 0 ].w, screenScales[ 0 ].h, currentNumVisibleSources );
+				renderStorage.checkRenewData( screenScales.get( 0 ).width(), screenScales.get( 0 ).height(), currentNumVisibleSources );
 				projector = createProjector( currentViewerState, requestedScreenScaleIndex, renderResult.getScreenImage(), 0, 0 );
 				requestNewFrameIfIncomplete = projectorFactory.requestNewFrameIfIncomplete();
 				renderingMayBeCancelled = !newFrame;
@@ -514,9 +419,7 @@ public class MultiResolutionRenderer
 	{
 		if ( renderingMayBeCancelled && projector != null )
 			projector.cancel();
-		for ( ScreenScale screenScale : screenScales )
-			if ( screenScale != null )
-				screenScale.pullScreenInterval();
+		screenScales.clearRequestedIntervals();
 		intervalMode = false;
 		newFrameRequest = true;
 		painterThread.requestRepaint();
@@ -528,8 +431,7 @@ public class MultiResolutionRenderer
 		{
 			if ( projector != null )
 				projector.cancel();
-			for ( final ScreenScale screenScale : screenScales )
-				screenScale.requestInterval( screenInterval );
+			screenScales.requestInterval( screenInterval );
 			intervalMode = true;
 			newIntervalRequest = true;
 			painterThread.requestRepaint();
@@ -538,20 +440,6 @@ public class MultiResolutionRenderer
 			requestRepaint();
 	}
 
-	private int suggestScreenScale( final Dimensions screenSize, final int numSources )
-	{
-		final double intervalRenderNanos = renderNanosPerPixelAndSource.getAverage() * Intervals.numElements( screenSize ) * numSources;
-		for ( int i = 0; i < screenScaleFactors.length - 1; i++ )
-		{
-			final double s = screenScaleFactors[ i ];
-			final double renderTime = intervalRenderNanos * s * s;
-			if ( renderTime <= targetRenderNanos )
-				return i;
-		}
-		return screenScaleFactors.length - 1;
-	}
-
-
 	/**
 	 * DON'T USE THIS.
 	 * <p>
@@ -579,7 +467,7 @@ public class MultiResolutionRenderer
 		 */
 //		CacheIoTiming.getIoTimeBudget().clear(); // clear time budget such that prefetching doesn't wait for loading blocks.
 
-		final AffineTransform3D screenScaleTransform = screenScales[ screenScaleIndex ].scale;
+		final AffineTransform3D screenScaleTransform = screenScales.get( screenScaleIndex ).scaleTransform();
 		final AffineTransform3D screenTransform = viewerState.getViewerTransform();
 		screenTransform.preConcatenate( screenScaleTransform );
 		screenTransform.translate( -offsetX, -offsetY, 0 );
@@ -593,12 +481,7 @@ public class MultiResolutionRenderer
 		return projector;
 	}
 
-
-
-
-
-
-	// =========== intervals =============
+	// =========== intervals ==========================================================================================
 
 	private RenderResult currentRenderResult;
 
@@ -636,20 +519,12 @@ public class MultiResolutionRenderer
 			if ( newInterval )
 			{
 				cacheControl.prepareNextFrame();
-				final double renderNanosPerPixel = currentNumVisibleSources * renderNanosPerPixelAndSource.getAverage();
-				for ( int i = currentScreenScaleIndex; i < screenScales.length; i++ )
-				{
-					final double renderTime = screenScales[ i ].estimateIntervalRenderNanos( renderNanosPerPixel );
-					if ( renderTime <= targetRenderNanos )
-					{
-						requestedIntervalScaleIndex = i;
-						break;
-					}
-				}
+				final double renderNanosPerPixel = renderNanosPerPixelAndSource.getAverage() * currentNumVisibleSources;
+				requestedIntervalScaleIndex = screenScales.suggestIntervalScreenScale( renderNanosPerPixel, targetRenderNanos, currentScreenScaleIndex );
 			}
 
 			createProjector = newInterval || ( requestedIntervalScaleIndex != currentIntervalScaleIndex );
-			screenScale = screenScales[ requestedIntervalScaleIndex ];
+			screenScale = screenScales.get( requestedIntervalScaleIndex );
 			requestedScreenInterval = screenScale.pullScreenInterval();
 
 			if ( createProjector )
@@ -658,14 +533,14 @@ public class MultiResolutionRenderer
 				intervalResult.init(
 						( int ) interval.dimension( 0 ),
 						( int ) interval.dimension( 1 ) );
-				final double intervalScale = screenScale.screenToViewerScale;
+				final double intervalScale = screenScale.scale();
 				intervalResult.setScaleFactor( intervalScale );
 				final double offsetX = interval.min( 0 );
 				final double offsetY = interval.min( 1 );
 				projector = createProjector( currentViewerState, requestedIntervalScaleIndex, intervalResult.getScreenImage(), offsetX, offsetY );
 
-				final Interval targetInterval = screenScales[ currentScreenScaleIndex ].scaleScreenInterval( requestedScreenInterval );
-				final double relativeScale = screenScaleFactors[ currentScreenScaleIndex ] / intervalScale;
+				final Interval targetInterval = screenScales.get( currentScreenScaleIndex ).scaleScreenInterval( requestedScreenInterval );
+				final double relativeScale = screenScales.get( currentScreenScaleIndex ).scale() / intervalScale;
 				final double tx = interval.min( 0 ) * relativeScale;
 				final double ty = interval.min( 1 ) * relativeScale;
 				intervalRenderData = new IntervalRenderData( targetInterval, tx, ty );
@@ -679,6 +554,7 @@ public class MultiResolutionRenderer
 
 		synchronized ( this )
 		{
+			// if rendering was not cancelled...
 			if ( success )
 			{
 				if ( createProjector )
@@ -729,19 +605,4 @@ public class MultiResolutionRenderer
 		requestedIntervalScaleIndex = intervalScaleIndex;
 		painterThread.requestRepaint();
 	}
-
-	private Interval scaleScreenInterval( final Interval requestedScreenInterval, final int intervalScaleIndex )
-	{
-		final int clipW = screenScales[ intervalScaleIndex ].w;
-		final int clipH = screenScales[ intervalScaleIndex ].h;
-		final double scale = screenScaleFactors[ intervalScaleIndex ];
-		// This is equivalent to
-		// Intervals.intersect( new FinalInterval( clipW, clipH ), Intervals.smallestContainingInterval( Intervals.scale( requestedScreenInterval, scale ) ) );
-		return Intervals.createMinMax(
-				Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 0 ) * scale ) ),
-				Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 1 ) * scale ) ),
-				Math.min( clipW - 1, ( int ) Math.ceil( requestedScreenInterval.max( 0 ) * scale ) ),
-				Math.min( clipH - 1, ( int ) Math.ceil( requestedScreenInterval.max( 1 ) * scale ) )
-		);
-	}
 }
diff --git a/src/main/java/bdv/viewer/render/ScreenScales.java b/src/main/java/bdv/viewer/render/ScreenScales.java
new file mode 100644
index 0000000000000000000000000000000000000000..f743bcf2734aa771d8fffc8826c10523bae26590
--- /dev/null
+++ b/src/main/java/bdv/viewer/render/ScreenScales.java
@@ -0,0 +1,198 @@
+package bdv.viewer.render;
+
+import java.util.ArrayList;
+import java.util.List;
+import net.imglib2.Interval;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.util.Intervals;
+
+public class ScreenScales
+{
+	private final List< ScreenScale > screenScales;
+
+	private int screenW = 0;
+
+	private int screenH = 0;
+
+	/**
+	 * @param screenScaleFactors
+	 * 		Scale factors from the viewer canvas to screen images of
+	 * 		different resolutions. A scale factor of 1 means 1 pixel in
+	 * 		the screen image is displayed as 1 pixel on the canvas, a
+	 * 		scale factor of 0.5 means 1 pixel in the screen image is
+	 * 		displayed as 2 pixel on the canvas, etc.
+	 */
+	public ScreenScales( final double[] screenScaleFactors )
+	{
+		screenScales = new ArrayList<>();
+		for ( double scale : screenScaleFactors )
+			screenScales.add( new ScreenScale( scale ) );
+	}
+
+	/**
+	 * Check whether the screen size was changed and revise {@link #screenScales} accordingly.
+	 *
+	 * @return whether the size was changed.
+	 */
+	public boolean checkResize( final int newScreenW, final int newScreenH )
+	{
+		if ( newScreenW != screenW || newScreenH != screenH )
+		{
+			screenW = newScreenW;
+			screenH = newScreenH;
+			screenScales.forEach( s -> s.resize( screenW, screenH ) );
+			return true;
+		}
+		return false;
+	}
+
+	public ScreenScale get( final int index )
+	{
+		return screenScales.get( index );
+	}
+
+	public int size()
+	{
+		return screenScales.size();
+	}
+
+	public int suggestScreenScale( final double renderNanosPerPixel, final double targetRenderNanos )
+	{
+		for ( int i = 0; i < screenScales.size() - 1; i++ )
+		{
+			final double renderTime = screenScales.get( i ).estimateRenderNanos( renderNanosPerPixel );
+			if ( renderTime <= targetRenderNanos )
+				return i;
+		}
+		return screenScales.size() - 1;
+	}
+
+	public int suggestIntervalScreenScale( final double renderNanosPerPixel, final double targetRenderNanos, final int minScreenScaleIndex )
+	{
+		for ( int i = minScreenScaleIndex; i < screenScales.size() - 1; i++ )
+		{
+			final double renderTime = screenScales.get( i ).estimateIntervalRenderNanos( renderNanosPerPixel );
+			if ( renderTime <= targetRenderNanos )
+				return i;
+		}
+		return screenScales.size() - 1;
+	}
+
+	public void requestInterval( final Interval screenInterval )
+	{
+		screenScales.forEach( s -> s.requestInterval( screenInterval ) );
+	}
+
+	public void clearRequestedIntervals()
+	{
+		screenScales.forEach( ScreenScale::pullScreenInterval );
+	}
+
+	public static class ScreenScale
+	{
+		/**
+		 * Scale factor from the viewer coordinates to target image of this screen scale.
+		 */
+		private final double scale;
+
+		/**
+		 * The width of the target image at this ScreenScale.
+		 */
+		private int w = 0;
+
+		/**
+		 * The height of the target image at this ScreenScale.
+		 */
+		private int h = 0;
+
+		/**
+		 * The transformation from viewer to target image coordinates at this ScreenScale.
+		 */
+		private final AffineTransform3D scaleTransform = new AffineTransform3D();
+
+		/**
+		 * Pending interval request.
+		 * This is in viewer coordinates.
+		 * To transform to target coordinates of this scale, use {@link #scaleScreenInterval}.
+		 */
+		private Interval requestedScreenInterval = null;
+
+		/**
+		 * @param scale
+		 * 		Scale factor from the viewer coordinates to target image of this screen scale/
+		 */
+		ScreenScale( final double scale )
+		{
+			this.scale = scale;
+		}
+
+		void resize( final int screenW, final int screenH )
+		{
+			w = ( int ) Math.ceil( scale * screenW );
+			h = ( int ) Math.ceil( scale * screenH );
+
+			scaleTransform.set( scale, 0, 0 );
+			scaleTransform.set( scale, 1, 1 );
+			scaleTransform.set( 0.5 * scale - 0.5, 0, 3 );
+			scaleTransform.set( 0.5 * scale - 0.5, 1, 3 );
+
+			requestedScreenInterval = null;
+		}
+
+		double estimateRenderNanos( final double renderNanosPerPixel )
+		{
+			return renderNanosPerPixel * w * h;
+		}
+
+		public void requestInterval( final Interval screenInterval )
+		{
+			requestedScreenInterval = requestedScreenInterval == null
+					? screenInterval
+					: Intervals.union( requestedScreenInterval, screenInterval );
+		}
+
+		public Interval pullScreenInterval()
+		{
+			final Interval interval = requestedScreenInterval;
+			requestedScreenInterval = null;
+			return interval;
+		}
+
+		public double estimateIntervalRenderNanos( final double renderNanosPerPixel )
+		{
+			return renderNanosPerPixel * Intervals.numElements( scaleScreenInterval( requestedScreenInterval ) );
+		}
+
+		public Interval scaleScreenInterval( final Interval requestedScreenInterval )
+		{
+			// This is equivalent to
+			// Intervals.intersect( new FinalInterval( w, h ), Intervals.smallestContainingInterval( Intervals.scale( requestedScreenInterval, screenToViewerScale ) ) );
+			return Intervals.createMinMax(
+					Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 0 ) * scale ) ),
+					Math.max( 0, ( int ) Math.floor( requestedScreenInterval.min( 1 ) * scale ) ),
+					Math.min( w - 1, ( int ) Math.ceil( requestedScreenInterval.max( 0 ) * scale ) ),
+					Math.min( h - 1, ( int ) Math.ceil( requestedScreenInterval.max( 1 ) * scale ) )
+			);
+		}
+
+		public int width()
+		{
+			return w;
+		}
+
+		public int height()
+		{
+			return h;
+		}
+
+		public double scale()
+		{
+			return scale;
+		}
+
+		public AffineTransform3D scaleTransform()
+		{
+			return scaleTransform;
+		}
+	}
+}