+ * The update of the current option has to be managed by outside logic that + * calls {@link #setOption} in response to {@code ActionEvent}s. + */ +class OptionButton extends JPanel +{ + final JButton button; + + private final Icon[] icons; + + private int currentOption = 0; + + public OptionButton( + final Icon[] icons, + final String[] tooltipTexts ) + { + super( new MigLayout( "ins 0, fillx, filly", "[]", "[]0lp![]" ) ); + + if ( icons == null || tooltipTexts == null ) + throw new NullPointerException(); + + if ( icons.length == 0 || icons.length != tooltipTexts.length ) + throw new IllegalArgumentException(); + + this.icons = icons; + + button = new JButton( icons[ 0 ] ); + button.setToolTipText( tooltipTexts[ 0 ] ); + setLook( button ); + + button.addPropertyChangeListener( e -> { + if ( e.getPropertyName().equals( "option" ) ) + { + button.setIcon( this.icons[ currentOption ] ); + button.setToolTipText( tooltipTexts[ currentOption ] ); + } + } ); + + this.add( button, "growx, center, wrap" ); + } + + public void setIcons( final Icon[] icons ) + { + if ( icons.length != this.icons.length ) + throw new IllegalArgumentException(); + + System.arraycopy( icons, 0, this.icons, 0, icons.length ); + + button.setIcon( icons[ currentOption ] ); + setLook( button ); + } + + public int getOption() + { + return currentOption; + } + + public void setOption( int option ) + { + if ( option < 0 || option >= icons.length ) + throw new IllegalArgumentException( "Invalid option index: " + option ); + final int oldOption = currentOption; + currentOption = option; + button.firePropertyChange( "option", oldOption, currentOption ); + } + + public void addActionListener( final ActionListener l ) + { + button.addActionListener( l ); + } + + public void removeActionListener( final ActionListener l ) + { + button.removeActionListener( l ); + } + + private void setLook( final JButton button ) + { + button.setMaximumSize( new Dimension( button.getIcon().getIconWidth(), button.getIcon().getIconHeight() ) ); + button.setBorderPainted( false ); + button.setFocusPainted( false ); + button.setContentAreaFilled( false ); + } +} diff --git a/src/main/java/bdv/ui/viewermodepanel/ToggleButton.java b/src/main/java/bdv/ui/viewermodepanel/ToggleButton.java index 48cc80a71..ac01aa91a 100644 --- a/src/main/java/bdv/ui/viewermodepanel/ToggleButton.java +++ b/src/main/java/bdv/ui/viewermodepanel/ToggleButton.java @@ -42,7 +42,7 @@ class ToggleButton extends JPanel private final String tooltipText; private final String selectedTooltipText; - private final JToggleButton button; + final JToggleButton button; public ToggleButton( final Icon icon, @@ -58,6 +58,8 @@ public ToggleButton( button.setSelectedIcon( selectedIcon ); setLook( button ); + button.addItemListener( e -> button.setToolTipText( isSelected() ? selectedTooltipText : tooltipText ) ); + this.add( button, "growx, center, wrap" ); } @@ -71,7 +73,6 @@ public void setIcons( final Icon defaultIcon, final Icon selectedIcon ) public void setSelected( final boolean selected ) { button.setSelected( selected ); - button.setToolTipText( selected ? selectedTooltipText : tooltipText ); } public boolean isSelected() diff --git a/src/main/java/bdv/util/AbstractSource.java b/src/main/java/bdv/util/AbstractSource.java index d5496e0b3..304dd106e 100644 --- a/src/main/java/bdv/util/AbstractSource.java +++ b/src/main/java/bdv/util/AbstractSource.java @@ -31,13 +31,16 @@ import java.util.function.Supplier; import bdv.viewer.Interpolation; +import bdv.viewer.MaskUtils; import bdv.viewer.Source; import mpicbg.spim.data.sequence.DefaultVoxelDimensions; -import mpicbg.spim.data.sequence.FinalVoxelDimensions; import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.type.Type; +import net.imglib2.type.mask.Masked; import net.imglib2.type.numeric.NumericType; +import net.imglib2.util.Cast; import net.imglib2.view.Views; public abstract class AbstractSource< T extends NumericType< T > > implements Source< T > @@ -107,6 +110,18 @@ public RealRandomAccessible< T > getInterpolatedSource( final int t, final int l return Views.interpolate( Views.extendZero( getSource( t, level ) ), interpolators.get( method ) ); } + @Override + public RandomAccessibleInterval< ? extends Masked< T > > getMaskedSource( final int t, final int level ) + { + return Masked.withConstant( getSource( t, level ), 1 ); + } + + @Override + public RealRandomAccessible< ? extends Masked< T > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method ) + { + return MaskUtils.extendAndInterpolateMasked( Cast.unchecked( getMaskedSource( t, level ) ), method ); + } + @Override public String getName() { diff --git a/src/main/java/bdv/util/PlaceHolderSource.java b/src/main/java/bdv/util/PlaceHolderSource.java index 55189f2fd..315688676 100644 --- a/src/main/java/bdv/util/PlaceHolderSource.java +++ b/src/main/java/bdv/util/PlaceHolderSource.java @@ -35,6 +35,7 @@ import bdv.viewer.Interpolation; import bdv.viewer.Source; import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.type.mask.Masked; /** * A dummy {@link Source} that represents a {@link BdvOverlay}. @@ -101,6 +102,18 @@ public RealRandomAccessible< Void > getInterpolatedSource( final int t, final in return null; } + @Override + public RandomAccessibleInterval< ? extends Masked< Void > > getMaskedSource( final int t, final int level ) + { + return null; + } + + @Override + public RealRandomAccessible< ? extends Masked< Void > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method ) + { + return null; + } + @Override public void getSourceTransform( final int t, final int level, final AffineTransform3D transform ) { diff --git a/src/main/java/bdv/util/RandomAccessibleIntervalMipmapSource4D.java b/src/main/java/bdv/util/RandomAccessibleIntervalMipmapSource4D.java index 792f1a354..89044ae17 100644 --- a/src/main/java/bdv/util/RandomAccessibleIntervalMipmapSource4D.java +++ b/src/main/java/bdv/util/RandomAccessibleIntervalMipmapSource4D.java @@ -153,14 +153,12 @@ private void loadTimepoint( final int timepointIndex ) currentTimePointIndex = timepointIndex; if ( isPresent( timepointIndex ) ) { - final T zero = getType().createVariable(); - zero.setZero(); for ( int level = 0; level < currentMipmaps.length; ++level ) { currentMipmaps[level] = Views.hyperSlice(mipmapSources[level], 3, timepointIndex ); for ( final Interpolation method : Interpolation.values() ) currentInterpolatedSources[ method.ordinal() ][ level ] = - Views.interpolate( Views.extendValue( currentMipmaps[level], zero ), interpolators.get( method ) ); + Views.interpolate( Views.extendZero( currentMipmaps[level] ), interpolators.get( method ) ); } } else diff --git a/src/main/java/bdv/util/RandomAccessibleIntervalSource.java b/src/main/java/bdv/util/RandomAccessibleIntervalSource.java index 4f5626e81..37d31e8b9 100644 --- a/src/main/java/bdv/util/RandomAccessibleIntervalSource.java +++ b/src/main/java/bdv/util/RandomAccessibleIntervalSource.java @@ -72,10 +72,8 @@ public RandomAccessibleIntervalSource( this.source = img; this.sourceTransform = sourceTransform; interpolatedSources = new RealRandomAccessible[ Interpolation.values().length ]; - final T zero = getType().createVariable(); - zero.setZero(); for ( final Interpolation method : Interpolation.values() ) - interpolatedSources[ method.ordinal() ] = Views.interpolate( Views.extendValue( source, zero ), interpolators.get( method ) ); + interpolatedSources[ method.ordinal() ] = Views.interpolate( Views.extendZero( source ), interpolators.get( method ) ); } @Override diff --git a/src/main/java/bdv/util/RandomAccessibleIntervalSource4D.java b/src/main/java/bdv/util/RandomAccessibleIntervalSource4D.java index a43e033ff..f4244e1b0 100644 --- a/src/main/java/bdv/util/RandomAccessibleIntervalSource4D.java +++ b/src/main/java/bdv/util/RandomAccessibleIntervalSource4D.java @@ -75,11 +75,9 @@ private void loadTimepoint( final int timepointIndex ) currentTimePointIndex = timepointIndex; if ( isPresent( timepointIndex ) ) { - final T zero = getType().createVariable(); - zero.setZero(); currentSource = Views.hyperSlice( source, 3, timepointIndex ); for ( final Interpolation method : Interpolation.values() ) - currentInterpolatedSources[ method.ordinal() ] = Views.interpolate( Views.extendValue( currentSource, zero ), interpolators.get( method ) ); + currentInterpolatedSources[ method.ordinal() ] = Views.interpolate( Views.extendZero( currentSource ), interpolators.get( method ) ); } else { diff --git a/src/main/java/bdv/util/RandomAccessibleSource.java b/src/main/java/bdv/util/RandomAccessibleSource.java index 144625a5d..474a342fa 100644 --- a/src/main/java/bdv/util/RandomAccessibleSource.java +++ b/src/main/java/bdv/util/RandomAccessibleSource.java @@ -34,6 +34,7 @@ import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.mask.Masked; import net.imglib2.type.numeric.NumericType; import net.imglib2.view.Views; @@ -90,12 +91,24 @@ public RandomAccessibleInterval< T > getSource( final int t, final int level ) return Views.interval( source, interval ); } + @Override + public RandomAccessibleInterval< ? extends Masked< T > > getMaskedSource( final int t, final int level ) + { + return Views.interval( Masked.withConstant( source, 1 ), interval ); + } + @Override public RealRandomAccessible< T > getInterpolatedSource( final int t, final int level, final Interpolation method ) { return interpolatedSources[ method.ordinal() ]; } + @Override + public RealRandomAccessible< ? extends Masked< T > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method ) + { + return Masked.withConstant( getInterpolatedSource( t, level, method ), 1 ); + } + @Override public synchronized void getSourceTransform( final int t, final int level, final AffineTransform3D transform ) { diff --git a/src/main/java/bdv/util/RandomAccessibleSource4D.java b/src/main/java/bdv/util/RandomAccessibleSource4D.java index ffabc89fd..fa2615ee5 100644 --- a/src/main/java/bdv/util/RandomAccessibleSource4D.java +++ b/src/main/java/bdv/util/RandomAccessibleSource4D.java @@ -36,6 +36,7 @@ import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.mask.Masked; import net.imglib2.type.numeric.NumericType; import net.imglib2.util.Intervals; import net.imglib2.view.Views; @@ -52,6 +53,8 @@ public class RandomAccessibleSource4D< T extends NumericType< T > > extends Abst private RandomAccessibleInterval< T > currentSource; + private RandomAccessibleInterval< ? extends Masked< T > > currentMaskedSource; + private final RealRandomAccessible< T >[] currentInterpolatedSources; private final AffineTransform3D sourceTransform; @@ -98,10 +101,9 @@ private void loadTimepoint( final int timepointIndex ) currentTimePointIndex = timepointIndex; if ( isPresent( timepointIndex ) ) { - final T zero = getType().createVariable(); - zero.setZero(); final RandomAccessible< T > slice = Views.hyperSlice( source, 3, timepointIndex ); currentSource = Views.interval( slice, timeSliceInterval ); + currentMaskedSource = Views.interval( Masked.withConstant( slice, 1 ), timeSliceInterval ); for ( final Interpolation method : Interpolation.values() ) currentInterpolatedSources[ method.ordinal() ] = Views.interpolate( slice, interpolators.get( method ) ); } @@ -126,6 +128,14 @@ public synchronized RandomAccessibleInterval< T > getSource( final int t, final return currentSource; } + @Override + public synchronized RandomAccessibleInterval< ? extends Masked< T > > getMaskedSource( final int t, final int level ) + { + if ( t != currentTimePointIndex ) + loadTimepoint( t ); + return currentMaskedSource; + } + @Override public synchronized RealRandomAccessible< T > getInterpolatedSource( final int t, final int level, final Interpolation method ) { @@ -134,6 +144,12 @@ public synchronized RealRandomAccessible< T > getInterpolatedSource( final int t return currentInterpolatedSources[ method.ordinal() ]; } + @Override + public RealRandomAccessible< ? extends Masked< T > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method ) + { + return Masked.withConstant( getInterpolatedSource( t, level, method ), 1 ); + } + @Override public synchronized void getSourceTransform( final int t, final int level, final AffineTransform3D transform ) { diff --git a/src/main/java/bdv/util/RealRandomAccessibleSource.java b/src/main/java/bdv/util/RealRandomAccessibleSource.java index 541b4a787..36c0ac6eb 100644 --- a/src/main/java/bdv/util/RealRandomAccessibleSource.java +++ b/src/main/java/bdv/util/RealRandomAccessibleSource.java @@ -6,13 +6,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -36,6 +36,7 @@ import net.imglib2.RealRandomAccessible; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.Type; +import net.imglib2.type.mask.Masked; import net.imglib2.view.Views; /** @@ -104,6 +105,18 @@ public RealRandomAccessible< T > getInterpolatedSource( final int t, final int l return accessible; } + @Override + public RandomAccessibleInterval< ? extends Masked< T > > getMaskedSource( final int t, final int level ) + { + return Views.interval( Views.raster( Masked.withConstant( accessible, 1 ) ), getInterval( t, level ) ); + } + + @Override + public RealRandomAccessible< ? extends Masked< T > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method ) + { + return Masked.withConstant( accessible, 1 ); + } + @Override public void getSourceTransform( final int t, final int level, final AffineTransform3D transform ) { diff --git a/src/main/java/bdv/viewer/BasicViewerState.java b/src/main/java/bdv/viewer/BasicViewerState.java index e31e8eb18..aa1b2b7ad 100644 --- a/src/main/java/bdv/viewer/BasicViewerState.java +++ b/src/main/java/bdv/viewer/BasicViewerState.java @@ -30,6 +30,8 @@ import bdv.util.Affine3DHelpers; import bdv.util.WrappedList; +import bdv.viewer.render.AccumulateProjectorARGB; +import bdv.viewer.render.AccumulateProjectorFactory; import gnu.trove.map.TObjectIntMap; import gnu.trove.map.hash.TObjectIntHashMap; import java.util.ArrayList; @@ -43,8 +45,11 @@ import java.util.Objects; import java.util.Set; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.ARGBType; + import org.scijava.listeners.Listeners; +import static bdv.viewer.ViewerStateChange.ACCUMULATE_PROJECTOR_CHANGED; import static bdv.viewer.ViewerStateChange.CURRENT_GROUP_CHANGED; import static bdv.viewer.ViewerStateChange.CURRENT_SOURCE_CHANGED; import static bdv.viewer.ViewerStateChange.CURRENT_TIMEPOINT_CHANGED; @@ -102,6 +107,11 @@ public class BasicViewerState implements ViewerState */ private DisplayMode displayMode; + /** + * The current AccumulateProjectorFactory. + */ + private AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory; + // -- sources -- private final List< SourceAndConverter< ? > > sources; @@ -149,6 +159,7 @@ public BasicViewerState() viewerTransform = new AffineTransform3D(); interpolation = Interpolation.NEARESTNEIGHBOR; displayMode = DisplayMode.SINGLE; + accumulateProjectorFactory = AccumulateProjectorARGB.factory; sources = new ArrayList<>(); unmodifiableSources = new UnmodifiableSources(); activeSources = new HashSet<>(); @@ -176,6 +187,7 @@ public BasicViewerState( final ViewerState other ) viewerTransform = other.getViewerTransform(); interpolation = other.getInterpolation(); displayMode = other.getDisplayMode(); + accumulateProjectorFactory = other.getAccumulateProjectorFactory(); sources = new ArrayList<>( other.getSources() ); unmodifiableSources = new UnmodifiableSources(); @@ -215,6 +227,7 @@ public void set( final ViewerState other ) viewerTransform.set( other.getViewerTransform() ); interpolation = other.getInterpolation(); displayMode = other.getDisplayMode(); + accumulateProjectorFactory = other.getAccumulateProjectorFactory(); sources.clear(); sources.addAll( other.getSources() ); @@ -297,6 +310,23 @@ public void setDisplayMode( final DisplayMode mode ) } } + @Override + public AccumulateProjectorFactory< ARGBType > getAccumulateProjectorFactory() + { + return accumulateProjectorFactory; + } + + @Override + public void setAccumulateProjectorFactory( final AccumulateProjectorFactory< ARGBType > factory ) + { + if ( accumulateProjectorFactory != factory ) + { + accumulateProjectorFactory = factory; + notifyListeners( ACCUMULATE_PROJECTOR_CHANGED ); + } + + } + @Override public int getNumTimepoints() { diff --git a/src/main/java/bdv/viewer/BlendMode.java b/src/main/java/bdv/viewer/BlendMode.java new file mode 100644 index 000000000..c4b2cd033 --- /dev/null +++ b/src/main/java/bdv/viewer/BlendMode.java @@ -0,0 +1,69 @@ +/* + * #%L + * BigDataViewer core classes with minimal dependencies. + * %% + * Copyright (C) 2012 - 2025 BigDataViewer developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package bdv.viewer; + +import bdv.viewer.render.AccumulateProjectorARGB; +import bdv.viewer.render.AccumulateProjectorFactory; +import bdv.viewer.render.AlphaWeightedAccumulateProjectorARGB; +import net.imglib2.type.numeric.ARGBType; + +/** + * Blending modes. + *
+ * The {@code BlendMode} is determined by the {@link AccumulateProjectorFactory}
+ * used for combining overlapping sources into the final screen image.
+ */
+public enum BlendMode
+{
+ SUM( "sum sources" ),
+ AVG( "average sources" ),
+ CUSTOM( "custom source accumulator" );
+
+ private final String name;
+
+ BlendMode( final String name )
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public static BlendMode of( final AccumulateProjectorFactory< ARGBType > factory )
+ {
+ if ( factory instanceof AccumulateProjectorARGB.Factory )
+ return BlendMode.SUM;
+ else if ( factory instanceof AlphaWeightedAccumulateProjectorARGB.Factory )
+ return BlendMode.AVG;
+ else
+ return BlendMode.CUSTOM;
+ }
+}
diff --git a/src/main/java/bdv/viewer/BlendModeSwitcher.java b/src/main/java/bdv/viewer/BlendModeSwitcher.java
new file mode 100644
index 000000000..c031221bf
--- /dev/null
+++ b/src/main/java/bdv/viewer/BlendModeSwitcher.java
@@ -0,0 +1,106 @@
+package bdv.viewer;
+
+import static bdv.viewer.BlendMode.AVG;
+import static bdv.viewer.BlendMode.CUSTOM;
+import static bdv.viewer.BlendMode.SUM;
+import static bdv.viewer.ViewerStateChange.ACCUMULATE_PROJECTOR_CHANGED;
+
+import org.scijava.listeners.Listeners;
+
+import bdv.viewer.render.AccumulateProjectorARGB;
+import bdv.viewer.render.AccumulateProjectorFactory;
+import bdv.viewer.render.AlphaWeightedAccumulateProjectorARGB;
+import net.imglib2.type.numeric.ARGBType;
+
+public class BlendModeSwitcher
+{
+ private final ViewerState state;
+
+ private AccumulateProjectorFactory< ARGBType > customFactory;
+
+ private BlendMode mode;
+
+ public BlendModeSwitcher( final ViewerState state )
+ {
+ this.state = state;
+ listeners = new Listeners.List<>();
+
+ state.changeListeners().add( c ->
+ {
+ if ( c == ACCUMULATE_PROJECTOR_CHANGED )
+ factoryChanged( state.getAccumulateProjectorFactory() );
+ } );
+ factoryChanged( state.getAccumulateProjectorFactory() );
+ }
+
+ /**
+ * {@code BlendModeChangeListener}s are notified about blend mode changes.
+ */
+ public interface BlendModeChangeListener
+ {
+ void blendModeChanged(BlendMode newMode);
+ }
+
+ private final Listeners.List< BlendModeChangeListener > listeners;
+
+ public Listeners< BlendModeChangeListener > changeListeners()
+ {
+ return listeners;
+ }
+
+ public BlendMode getCurrentMode()
+ {
+ return mode;
+ }
+
+ public void switchToNextMode()
+ {
+ mode = next();
+ state.setAccumulateProjectorFactory( factory() );
+ listeners.list.forEach( l -> l.blendModeChanged( mode ) );
+ }
+
+ private BlendMode next()
+ {
+ switch ( mode )
+ {
+ case SUM:
+ return AVG;
+ case AVG:
+ return customFactory != null ? CUSTOM : SUM;
+ case CUSTOM:
+ return SUM;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private AccumulateProjectorFactory< ARGBType > factory()
+ {
+ switch ( mode )
+ {
+ case SUM:
+ return AccumulateProjectorARGB.factory;
+ case AVG:
+ return AlphaWeightedAccumulateProjectorARGB.factory;
+ case CUSTOM:
+ return customFactory;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private void factoryChanged( final AccumulateProjectorFactory< ARGBType > factory )
+ {
+ final BlendMode oldMode = mode;
+ mode = BlendMode.of( factory );
+
+ if ( mode == CUSTOM )
+ customFactory = factory;
+
+ if ( mode != oldMode )
+ {
+ listeners.list.forEach( l -> l.blendModeChanged( mode ) );
+ }
+ }
+}
diff --git a/src/main/java/bdv/viewer/InteractiveDisplayCanvas.java b/src/main/java/bdv/viewer/InteractiveDisplayCanvas.java
index 3850e4eda..a53702984 100644
--- a/src/main/java/bdv/viewer/InteractiveDisplayCanvas.java
+++ b/src/main/java/bdv/viewer/InteractiveDisplayCanvas.java
@@ -28,9 +28,11 @@
*/
package bdv.viewer;
+import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
+import java.awt.Graphics2D;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusListener;
@@ -207,6 +209,8 @@ public void setTransformEventHandler( final TransformEventHandler transformEvent
@Override
public void paintComponent( final Graphics g )
{
+ g.setColor( Color.BLACK );
+ g.fillRect( 0, 0, getWidth(), getHeight() );
overlayRenderers.list.forEach( r -> r.drawOverlays( g ) );
}
diff --git a/src/main/java/bdv/viewer/MaskUtils.java b/src/main/java/bdv/viewer/MaskUtils.java
new file mode 100644
index 000000000..bb46e55ad
--- /dev/null
+++ b/src/main/java/bdv/viewer/MaskUtils.java
@@ -0,0 +1,29 @@
+package bdv.viewer;
+
+import net.imglib2.RandomAccessible;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.RealRandomAccessible;
+import net.imglib2.interpolation.InterpolatorFactory;
+import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory;
+import net.imglib2.type.Type;
+import net.imglib2.type.mask.Masked;
+import net.imglib2.type.mask.interpolation.MaskedClampingNLinearInterpolatorFactory;
+import net.imglib2.type.numeric.NumericType;
+import net.imglib2.view.Views;
+
+public class MaskUtils
+{
+ /**
+ * Zero-extend (value and mask are set to zero) and interpolate a {@code RandomAccessibleInterval
+ * By default, this is just {@link #getSource} augmented with a constant
+ * mask {@code 1.0}.
+ *
+ * @param t
+ * timepoint index
+ * @param level
+ * mipmap level
+ * @return the masked stack
+ */
+ default RandomAccessibleInterval< ? extends Masked< T > > getMaskedSource( int t, int level ) {
+ return Masked.withConstant( getSource( t, level ), 1 );
+ }
+
+ /**
+ * Get a {@code Masked} version of the {@link #getInterpolatedSource 3D
+ * stack} at timepoint index t, extended to infinity and interpolated.
+ *
+ * By default, this is just {@link #getInterpolatedSource} augmented with a
+ * constant mask {@code 1.0}.
+ *
+ * However, most sources should override this to extend {@link
+ * #getMaskedSource(int, int)} to infinity (with a {@code 0} mask) and
+ * interpolate.
+ *
+ * @param t
+ * timepoint index
+ * @param level
+ * mipmap level
+ * @param method
+ * interpolation method to use
+ * @return the extended and interpolated {@link RandomAccessible stack}.
+ */
+ default RealRandomAccessible< ? extends Masked< T > > getInterpolatedMaskedSource( int t, int level, final Interpolation method ) {
+ return Masked.withConstant( getInterpolatedSource( t, level, method ), 1 );
+ }
}
diff --git a/src/main/java/bdv/viewer/SynchronizedViewerState.java b/src/main/java/bdv/viewer/SynchronizedViewerState.java
index 655fbae13..6dbe28949 100644
--- a/src/main/java/bdv/viewer/SynchronizedViewerState.java
+++ b/src/main/java/bdv/viewer/SynchronizedViewerState.java
@@ -34,7 +34,9 @@
import java.util.List;
import java.util.Set;
+import bdv.viewer.render.AccumulateProjectorFactory;
import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
import org.scijava.listeners.Listeners;
@@ -126,6 +128,18 @@ public synchronized void setDisplayMode( final DisplayMode mode )
state.setDisplayMode( mode );
}
+ @Override
+ public synchronized AccumulateProjectorFactory< ARGBType > getAccumulateProjectorFactory()
+ {
+ return state.getAccumulateProjectorFactory();
+ }
+
+ @Override
+ public synchronized void setAccumulateProjectorFactory( final AccumulateProjectorFactory< ARGBType > factory )
+ {
+ state.setAccumulateProjectorFactory( factory );
+ }
+
@Override
public synchronized int getNumTimepoints()
{
diff --git a/src/main/java/bdv/viewer/UnmodifiableViewerState.java b/src/main/java/bdv/viewer/UnmodifiableViewerState.java
index c31706780..c8249a967 100644
--- a/src/main/java/bdv/viewer/UnmodifiableViewerState.java
+++ b/src/main/java/bdv/viewer/UnmodifiableViewerState.java
@@ -32,7 +32,11 @@
import java.util.Comparator;
import java.util.List;
import java.util.Set;
+
+import bdv.viewer.render.AccumulateProjectorFactory;
import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
+
import org.scijava.listeners.Listeners;
/**
@@ -86,6 +90,18 @@ public void setDisplayMode( final DisplayMode mode )
throw new UnsupportedOperationException();
}
+ @Override
+ public AccumulateProjectorFactory< ARGBType > getAccumulateProjectorFactory()
+ {
+ return state.getAccumulateProjectorFactory();
+ }
+
+ @Override
+ public void setAccumulateProjectorFactory( final AccumulateProjectorFactory< ARGBType > factory )
+ {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public int getNumTimepoints()
{
diff --git a/src/main/java/bdv/viewer/ViewerFrame.java b/src/main/java/bdv/viewer/ViewerFrame.java
index 3e32337bd..3839a6e32 100644
--- a/src/main/java/bdv/viewer/ViewerFrame.java
+++ b/src/main/java/bdv/viewer/ViewerFrame.java
@@ -109,7 +109,7 @@ public ViewerFrame(
final ViewerOptions optional )
{
// super( "BigDataViewer", GuiUtil.getSuitableGraphicsConfiguration( GuiUtil.ARGB_COLOR_MODEL ) );
- super( "BigDataViewer", AWTUtils.getSuitableGraphicsConfiguration( AWTUtils.RGB_COLOR_MODEL ) );
+ super( "BigDataViewer", AWTUtils.getSuitableGraphicsConfiguration( AWTUtils.ARGB_COLOR_MODEL ) );
this.keymapManager = keymapManager;
this.appearanceManager = appearanceManager;
viewer = new ViewerPanel( sources, numTimepoints, cacheControl, optional );
diff --git a/src/main/java/bdv/viewer/ViewerPanel.java b/src/main/java/bdv/viewer/ViewerPanel.java
index ac33e00d3..f28c70f6e 100644
--- a/src/main/java/bdv/viewer/ViewerPanel.java
+++ b/src/main/java/bdv/viewer/ViewerPanel.java
@@ -229,6 +229,7 @@ public ViewerPanel( final List< SourceAndConverter< ? > > sources, final int num
options = optional.values;
state = setupState( sources, numTimepoints, options.getNumSourceGroups() );
+ state.setAccumulateProjectorFactory( options.getAccumulateProjectorFactory() );
deprecatedState = new bdv.viewer.state.ViewerState( state );
multiBoxOverlayRenderer = new MultiBoxOverlayRenderer();
@@ -255,7 +256,6 @@ public ViewerPanel( final List< SourceAndConverter< ? > > sources, final int num
options.getNumRenderingThreads(),
renderingExecutorService,
options.isUseVolatileIfAvailable(),
- options.getAccumulateProjectorFactory(),
cacheControl );
display.addHandler( mouseCoordinates );
@@ -588,6 +588,10 @@ public void viewerStateChanged( final ViewerStateChange change )
interpolationModeListeners.list.forEach( l -> l.interpolationModeChanged( interpolation ) );
requestRepaint();
break;
+ case ACCUMULATE_PROJECTOR_CHANGED:
+ showMessage( BlendMode.of( state.getAccumulateProjectorFactory() ).getName() );
+ requestRepaint();
+ break;
case NUM_TIMEPOINTS_CHANGED:
{
final int numTimepoints = state.getNumTimepoints();
diff --git a/src/main/java/bdv/viewer/ViewerState.java b/src/main/java/bdv/viewer/ViewerState.java
index d0d1bd62e..19b9bb3bf 100644
--- a/src/main/java/bdv/viewer/ViewerState.java
+++ b/src/main/java/bdv/viewer/ViewerState.java
@@ -32,7 +32,11 @@
import java.util.Comparator;
import java.util.List;
import java.util.Set;
+
+import bdv.viewer.render.AccumulateProjectorFactory;
import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
+
import org.scijava.listeners.Listeners;
/**
@@ -97,6 +101,27 @@ public interface ViewerState
*/
void setDisplayMode( DisplayMode mode );
+ /**
+ * Get the current {@code AccumulateProjectorFactory}.
+ *
+ * This factory provides AccumulateProjectors for blending rendered
+ * overlapping sources into the final screen image.
+ *
+ * @return current AccumulateProjector factory
+ */
+ AccumulateProjectorFactory< ARGBType > getAccumulateProjectorFactory();
+
+ /**
+ * Set the {@code AccumulateProjectorFactory}.
+ *
+ * This factory provides AccumulateProjectors for blending rendered
+ * overlapping sources into the final screen image. This can be used for
+ * switching between "sum" and "average" blending mode for example.
+ *
+ * @param factory AccumulateProjector factory to set
+ */
+ void setAccumulateProjectorFactory( AccumulateProjectorFactory< ARGBType > factory );
+
/**
* Get the number of timepoints.
*
diff --git a/src/main/java/bdv/viewer/ViewerStateChange.java b/src/main/java/bdv/viewer/ViewerStateChange.java
index 384764aa7..46548f670 100644
--- a/src/main/java/bdv/viewer/ViewerStateChange.java
+++ b/src/main/java/bdv/viewer/ViewerStateChange.java
@@ -47,6 +47,7 @@ public enum ViewerStateChange
VISIBILITY_CHANGED,
DISPLAY_MODE_CHANGED,
INTERPOLATION_CHANGED,
+ ACCUMULATE_PROJECTOR_CHANGED,
NUM_TIMEPOINTS_CHANGED,
CURRENT_TIMEPOINT_CHANGED,
VIEWER_TRANSFORM_CHANGED;
diff --git a/src/main/java/bdv/viewer/render/AbstractVolatileHierarchyProjector.java b/src/main/java/bdv/viewer/render/AbstractVolatileHierarchyProjector.java
new file mode 100644
index 000000000..7a2267a80
--- /dev/null
+++ b/src/main/java/bdv/viewer/render/AbstractVolatileHierarchyProjector.java
@@ -0,0 +1,285 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2025 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.render;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.imglib2.FinalInterval;
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessible;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.Volatile;
+import net.imglib2.cache.iotiming.CacheIoTiming;
+import net.imglib2.cache.iotiming.IoStatistics;
+import net.imglib2.converter.Converter;
+import net.imglib2.type.operators.SetZero;
+import net.imglib2.util.Intervals;
+import net.imglib2.util.StopWatch;
+import net.imglib2.view.Views;
+
+/**
+ * {@link VolatileProjector} for a hierarchy of {@link Volatile} inputs. After each
+ * {@link #map()} call, the projector has a {@link #isValid() state} that
+ * signalizes whether all projected pixels were perfect.
+ *
+ * @author Stephan Saalfeld
+ * @author Tobias Pietzsch
+ */
+abstract class AbstractVolatileHierarchyProjector< A, B extends SetZero > implements VolatileProjector
+{
+ /**
+ * A converter from the source pixel type to the target pixel type.
+ */
+ final Converter< ? super A, B > converter;
+
+ /**
+ * The target interval. Pixels of the target interval should be set by
+ * {@link #map}
+ */
+ private final RandomAccessibleInterval< B > target;
+
+ /**
+ * List of source resolutions starting with the optimal resolution at index
+ * 0. During each {@link #map(boolean)}, for every pixel, resolution levels
+ * are successively queried until a valid pixel is found.
+ */
+ private final List< RandomAccessible< A > > sources;
+
+ /**
+ * Records, for every target pixel, the best (smallest index) source
+ * resolution level that has provided a valid value. Only better (lower
+ * index) resolutions are re-tried in successive {@link #map(boolean)}
+ * calls.
+ */
+ final byte[] mask;
+
+ /**
+ * {@code true} iff all target pixels were rendered with valid data from the
+ * optimal resolution level (level {@code 0}).
+ */
+ private volatile boolean valid = false;
+
+ /**
+ * How many levels (starting from level {@code 0}) have to be re-rendered in
+ * the next rendering pass, i.e., {@code map()} call.
+ */
+ private int numInvalidLevels;
+
+ /**
+ * Source interval which will be used for rendering. This is the 2D target
+ * interval expanded to source dimensionality (usually 3D) with
+ * {@code min=max=0} in the additional dimensions.
+ */
+ private final FinalInterval sourceInterval;
+
+ /**
+ * Time needed for rendering the last frame, in nano-seconds.
+ * This does not include time spent in blocking IO.
+ */
+ private long lastFrameRenderNanoTime;
+
+ /**
+ * Time spent in blocking IO rendering the last frame, in nano-seconds.
+ */
+ private long lastFrameIoNanoTime; // TODO move to derived implementation for local sources only
+
+ /**
+ * temporary variable to store the number of invalid pixels in the current
+ * rendering pass.
+ */
+ private int numInvalidPixels;
+
+ /**
+ * Flag to indicate that someone is trying to {@link #cancel()} rendering.
+ */
+ private volatile boolean canceled = false;
+
+ AbstractVolatileHierarchyProjector(
+ final List< ? extends RandomAccessible< A > > sources,
+ final Converter< ? super A, B > converter,
+ final RandomAccessibleInterval< B > target,
+ final byte[] maskArray)
+ {
+ this.converter = converter;
+ this.target = target;
+ this.sources = new ArrayList<>( sources );
+ numInvalidLevels = sources.size();
+ mask = maskArray;
+
+ final int n = Math.max( 2, sources.get( 0 ).numDimensions() );
+ final long[] min = new long[ n ];
+ final long[] max = new long[ n ];
+ min[ 0 ] = target.min( 0 );
+ max[ 0 ] = target.max( 0 );
+ min[ 1 ] = target.min( 1 );
+ max[ 1 ] = target.max( 1 );
+ sourceInterval = new FinalInterval( min, max );
+
+ lastFrameRenderNanoTime = -1;
+ clearMask();
+ }
+
+ @Override
+ public void cancel()
+ {
+ canceled = true;
+ }
+
+ @Override
+ public long getLastFrameRenderNanoTime()
+ {
+ return lastFrameRenderNanoTime;
+ }
+
+ public long getLastFrameIoNanoTime()
+ {
+ return lastFrameIoNanoTime;
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ return valid;
+ }
+
+ /**
+ * Set all pixels in target to 100% transparent zero, and mask to all
+ * Integer.MAX_VALUE.
+ */
+ public void clearMask()
+ {
+ final int size = ( int ) Intervals.numElements( target );
+ Arrays.fill( mask, 0, size, Byte.MAX_VALUE );
+ numInvalidLevels = sources.size();
+ }
+
+ /**
+ * Clear target pixels that were never written.
+ */
+ private void clearUntouchedTargetPixels()
+ {
+ int i = 0;
+ for ( final B t : Views.flatIterable( target ) )
+ if ( mask[ i++ ] == Byte.MAX_VALUE )
+ t.setZero();
+ }
+
+ @Override
+ public boolean map( final boolean clearUntouchedTargetPixels )
+ {
+ if ( canceled )
+ return false;
+
+ valid = false;
+
+ final StopWatch stopWatch = StopWatch.createAndStart();
+ final IoStatistics iostat = CacheIoTiming.getIoStatistics();
+ final long startTimeIo = iostat.getIoNanoTime();
+
+ /*
+ * After the for loop, resolutionLevel is the highest (coarsest)
+ * resolution for which all pixels could be filled from valid data. This
+ * means that in the next pass, i.e., map() call, levels up to
+ * resolutionLevel have to be re-rendered.
+ */
+ int resolutionLevel;
+ for ( resolutionLevel = 0; resolutionLevel < numInvalidLevels; ++resolutionLevel )
+ {
+ numInvalidPixels = 0;
+ map( ( byte ) resolutionLevel);
+ if ( canceled )
+ return false;
+ if ( numInvalidPixels == 0 )
+ // if this pass was all valid
+ numInvalidLevels = resolutionLevel;
+ }
+
+ if ( clearUntouchedTargetPixels && numInvalidPixels != 0 && !canceled )
+ clearUntouchedTargetPixels();
+
+ lastFrameIoNanoTime = iostat.getIoNanoTime() - startTimeIo;
+ final long lastFrameTime = stopWatch.nanoTime();
+
+ // TODO (FORKJOIN): This is inaccurate now, should only use the io time used by current thread
+ // --> requires additional API in IoStatistics (imglib2-cache)
+ lastFrameRenderNanoTime = lastFrameTime - lastFrameIoNanoTime;
+
+ valid = numInvalidLevels == 0;
+
+ return !canceled;
+ }
+
+ /**
+ * Copy lines from {@code y = startHeight} up to {@code endHeight}
+ * (exclusive) from source {@code resolutionIndex} to target. Check after
+ * each line whether rendering was {@link #cancel() canceled}.
+ *
+ * Only valid source pixels with a current mask value
+ * {@code mask>resolutionIndex} are copied to target, and their mask value
+ * is set to {@code mask=resolutionIndex}. Invalid source pixels are
+ * ignored. Pixels with {@code mask<=resolutionIndex} are ignored, because
+ * they have already been written to target during a previous pass.
+ *
+ *
+ * @param resolutionIndex
+ * index of source resolution level
+ */
+ private void map( final byte resolutionIndex )
+ {
+ if ( canceled )
+ return;
+
+ final RandomAccess< B > targetRandomAccess = target.randomAccess( target );
+ final RandomAccess< A > sourceRandomAccess = sources.get( resolutionIndex ).randomAccess( sourceInterval );
+ final int width = ( int ) target.dimension( 0 );
+ final int height = ( int ) target.dimension( 1 );
+ final long[] smin = Intervals.minAsLongArray( sourceInterval );
+ int myNumInvalidPixels = 0;
+
+ for ( int y = 0; y < height; ++y )
+ {
+ // TODO (FORKJOIN) With tiles being granular enough, probably
+ // projectors shouldn't check after each line for cancellation
+ // (maybe not at all).
+ if ( canceled )
+ return;
+
+ sourceRandomAccess.setPosition( smin );
+ targetRandomAccess.setPosition( smin );
+ myNumInvalidPixels += processLine( sourceRandomAccess, targetRandomAccess, resolutionIndex, width, y );
+ ++smin[ 1 ];
+ }
+
+ numInvalidPixels += myNumInvalidPixels;
+ }
+
+ abstract int processLine(RandomAccess< A > sourceRandomAccess, RandomAccess< B > targetRandomAccess, byte resolutionIndex, int width, int y );
+}
diff --git a/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java b/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
index 017fe17c4..2ba924a49 100644
--- a/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
+++ b/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
@@ -270,14 +270,10 @@ protected void accumulate( final Cursor< ? extends ARGBType >[] accesses, final
gSum += g;
bSum += b;
}
- if ( aSum > 255 )
- aSum = 255;
- if ( rSum > 255 )
- rSum = 255;
- if ( gSum > 255 )
- gSum = 255;
- if ( bSum > 255 )
- bSum = 255;
+ aSum = Math.min( 255, aSum );
+ rSum = Math.min( 255, rSum );
+ gSum = Math.min( 255, gSum );
+ bSum = Math.min( 255, bSum );
target.set( ARGBType.rgba( rSum, gSum, bSum, aSum ) );
}
}
diff --git a/src/main/java/bdv/viewer/render/AccumulateProjectorFactory.java b/src/main/java/bdv/viewer/render/AccumulateProjectorFactory.java
index 3d6488e7b..6d32825f5 100644
--- a/src/main/java/bdv/viewer/render/AccumulateProjectorFactory.java
+++ b/src/main/java/bdv/viewer/render/AccumulateProjectorFactory.java
@@ -74,6 +74,20 @@ default VolatileProjector createProjector(
return createAccumulateProjector( sp, spimSources, si, targetScreenImage, numThreads, executorService );
}
+ /**
+ * Returns {@code true} if projectors created by this factory make use of
+ * alpha masks.
+ *
+ * (In that case, individual sources should be rendered using
+ * {@link Source#getInterpolatedMaskedSource} instead of
+ * {@link Source#getInterpolatedSource}.)
+ *
+ * @return true, if projectors created by this factory make use of alpha masks
+ */
+ default boolean requiresMaskedSources() {
+ return false;
+ }
+
/**
* @deprecated Use {@link #createProjector(List, List, List, RandomAccessibleInterval, int, ExecutorService)} instead.
* The new variant of the method is named "createProjector" instead of
diff --git a/src/main/java/bdv/viewer/render/AlphaWeightedAccumulateProjectorARGB.java b/src/main/java/bdv/viewer/render/AlphaWeightedAccumulateProjectorARGB.java
new file mode 100644
index 000000000..6ae10b139
--- /dev/null
+++ b/src/main/java/bdv/viewer/render/AlphaWeightedAccumulateProjectorARGB.java
@@ -0,0 +1,308 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2025 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.render;
+
+import bdv.viewer.SourceAndConverter;
+import bdv.viewer.render.ProjectorUtils.ArrayData;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinTask;
+import java.util.stream.Collectors;
+import net.imglib2.Cursor;
+import net.imglib2.Interval;
+import net.imglib2.RandomAccessible;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.util.Intervals;
+import net.imglib2.util.StopWatch;
+
+public class AlphaWeightedAccumulateProjectorARGB
+{
+ public static class Factory implements AccumulateProjectorFactory< ARGBType >
+ {
+ @Override
+ public VolatileProjector createProjector(
+ final List< VolatileProjector > sourceProjectors,
+ final List< SourceAndConverter< ? > > sources,
+ final List< ? extends RandomAccessible< ? extends ARGBType > > sourceScreenImages,
+ final RandomAccessibleInterval< ARGBType > targetScreenImage,
+ final int numThreads,
+ final ExecutorService executorService )
+ {
+ final ProjectorData projectorData = getProjectorData( sourceScreenImages, targetScreenImage );
+ if ( projectorData == null )
+ return new AccumulateProjectorARGBGeneric( sourceProjectors, sourceScreenImages, targetScreenImage );
+ else
+ return new AccumulateProjectorARGBArrayData( sourceProjectors, projectorData );
+ }
+
+ @Override
+ public boolean requiresMaskedSources() {
+ return true;
+ }
+ }
+
+ public static AccumulateProjectorFactory< ARGBType > factory = new Factory();
+
+ private static ProjectorData getProjectorData(
+ final List< ? extends RandomAccessible< ? extends ARGBType > > sources,
+ final RandomAccessibleInterval< ARGBType > target )
+ {
+ final ArrayData targetData = ProjectorUtils.getARGBArrayData( target );
+ if ( targetData == null )
+ return null;
+
+ final int numSources = sources.size();
+ final List< int[] > sourceData = new ArrayList<>( numSources );
+ for ( int i = 0; i < numSources; ++i )
+ {
+ final RandomAccessible< ? extends ARGBType > source = sources.get( i );
+ if ( ! ( source instanceof RandomAccessibleInterval ) )
+ return null;
+ if ( ! Intervals.equals( target, ( Interval ) source ) )
+ return null;
+ final int[] data = ProjectorUtils.getARGBArrayImgData( source );
+ if ( data == null )
+ return null;
+ sourceData.add( data );
+ }
+
+ return new ProjectorData( targetData, sourceData );
+ }
+
+ private static class ProjectorData
+ {
+ private final ArrayData targetData;
+ private final List< int[] > sourceData;
+
+ ProjectorData( final ArrayData targetData, final List< int[] > sourceData )
+ {
+ this.targetData = targetData;
+ this.sourceData = sourceData;
+ }
+
+ ArrayData targetData()
+ {
+ return targetData;
+ }
+
+ List< int[] > sourceData()
+ {
+ return sourceData;
+ }
+ }
+
+ private static class AccumulateProjectorARGBArrayData implements VolatileProjector
+ {
+ /**
+ * Projectors that render the source images to accumulate.
+ * For every rendering pass, ({@link VolatileProjector#map(boolean)}) is run on each source projector that is not yet {@link VolatileProjector#isValid() valid}.
+ */
+ private List< VolatileProjector > sourceProjectors;
+
+ /**
+ * The source images to accumulate
+ */
+ private final List< int[] > sources;
+
+ /**
+ * The target interval.
+ */
+ private final ArrayData target;
+
+ /**
+ * Time needed for rendering the last frame, in nano-seconds.
+ */
+ private long lastFrameRenderNanoTime;
+
+ private volatile boolean canceled = false;
+
+ private volatile boolean valid = false;
+
+ public AccumulateProjectorARGBArrayData(
+ final List< VolatileProjector > sourceProjectors,
+ final ProjectorData projectorData )
+ {
+ this.sourceProjectors = sourceProjectors;
+ this.target = projectorData.targetData();
+ this.sources = projectorData.sourceData();
+ }
+
+ @Override
+ public boolean map( final boolean clearUntouchedTargetPixels )
+ {
+ if ( canceled )
+ return false;
+
+ if ( isValid() )
+ return true;
+
+ final StopWatch stopWatch = StopWatch.createAndStart();
+
+ if ( target.size() < Tiling.MIN_ACCUMULATE_FORK_SIZE )
+ {
+ sourceProjectors.forEach( p -> p.map( clearUntouchedTargetPixels ) );
+ }
+ else
+ {
+ ForkJoinTask.invokeAll(
+ sourceProjectors.stream()
+ .map( p -> ForkJoinTask.adapt( () -> p.map( clearUntouchedTargetPixels ) ) )
+ .collect( Collectors.toList() ) );
+ }
+ if ( canceled )
+ return false;
+ mapAccumulate();
+ sourceProjectors = sourceProjectors.stream()
+ .filter( p -> !p.isValid() )
+ .collect( Collectors.toList() );
+ lastFrameRenderNanoTime = stopWatch.nanoTime();
+ valid = sourceProjectors.isEmpty();
+ return !canceled;
+ }
+
+ /**
+ * Accumulate pixels of all sources to target. Before starting, check
+ * whether rendering was {@link #cancel() canceled}.
+ */
+ private void mapAccumulate()
+ {
+ if ( canceled )
+ return;
+
+ final int numSources = sources.size();
+ final int[] acc = new int[ target.width() << 2 ];
+ for ( int y = 0; y < target.height(); ++y )
+ {
+ final int oTarget = ( y + target.oy() ) * target.stride() + target.ox();
+ final int oSource = y * target.width();
+ Arrays.fill( acc, 0 );
+ for ( int s = 0; s < numSources; ++s )
+ {
+ final int[] source = sources.get( s );
+ for ( int x = 0; x < target.width(); ++x )
+ {
+ final int value = source[ oSource + x ];
+ final int alpha = ARGBType.alpha( value );
+ final int red = ARGBType.red( value );
+ final int green = ARGBType.green( value );
+ final int blue = ARGBType.blue( value );
+ acc[ ( x << 2 ) ] += alpha;
+ acc[ ( x << 2 ) + 1 ] += red * alpha;
+ acc[ ( x << 2 ) + 2 ] += green * alpha;
+ acc[ ( x << 2 ) + 3 ] += blue * alpha;
+ }
+ }
+ for ( int x = 0; x < target.width(); ++x )
+ {
+ int aSum = acc[ ( x << 2 ) ];
+ final int rSum;
+ final int gSum;
+ final int bSum;
+ if ( aSum == 0 ) {
+ rSum = 0;
+ gSum = 0;
+ bSum = 0;
+ } else {
+ rSum = Math.min( 255, acc[ ( x << 2 ) + 1 ] / aSum );
+ gSum = Math.min( 255, acc[ ( x << 2 ) + 2 ] / aSum );
+ bSum = Math.min( 255, acc[ ( x << 2 ) + 3 ] / aSum );
+ aSum = Math.min( 255, aSum );
+ }
+ target.data()[ oTarget + x ] = ARGBType.rgba( rSum, gSum, bSum, aSum );
+ }
+ }
+ }
+
+ @Override
+ public void cancel()
+ {
+ canceled = true;
+ for ( final VolatileProjector p : sourceProjectors )
+ p.cancel();
+ }
+
+ @Override
+ public long getLastFrameRenderNanoTime()
+ {
+ return lastFrameRenderNanoTime;
+ }
+
+ @Override
+ public boolean isValid()
+ {
+ return valid;
+ }
+ }
+
+ private static class AccumulateProjectorARGBGeneric extends AccumulateProjector< ARGBType, ARGBType >
+ {
+ public AccumulateProjectorARGBGeneric(
+ final List< VolatileProjector > sourceProjectors,
+ final List< ? extends RandomAccessible< ? extends ARGBType > > sources,
+ final RandomAccessibleInterval< ARGBType > target )
+ {
+ super( sourceProjectors, sources, target );
+ }
+
+ @Override
+ protected void accumulate( final Cursor< ? extends ARGBType >[] accesses, final ARGBType target )
+ {
+ int aSum = 0, rSum = 0, gSum = 0, bSum = 0;
+ for ( final Cursor< ? extends ARGBType > access : accesses )
+ {
+ final int value = access.get().get();
+ final int alpha = ARGBType.alpha( value );
+ final int red = ARGBType.red( value );
+ final int green = ARGBType.green( value );
+ final int blue = ARGBType.blue( value );
+ aSum += alpha;
+ rSum += red * alpha;
+ gSum += green * alpha;
+ bSum += blue * alpha;
+ }
+ if ( aSum == 0 )
+ {
+ rSum = 0;
+ gSum = 0;
+ bSum = 0;
+ }
+ else
+ {
+ aSum = Math.min( 255, aSum );
+ rSum = Math.min( 255, rSum / aSum );
+ gSum = Math.min( 255, gSum / aSum );
+ bSum = Math.min( 255, bSum / aSum );
+ }
+ target.set( ARGBType.rgba( rSum, gSum, bSum, aSum ) );
+ }
+ }
+}
diff --git a/src/main/java/bdv/viewer/render/MaskedVolatileHierarchyProjector.java b/src/main/java/bdv/viewer/render/MaskedVolatileHierarchyProjector.java
new file mode 100644
index 000000000..a71488af7
--- /dev/null
+++ b/src/main/java/bdv/viewer/render/MaskedVolatileHierarchyProjector.java
@@ -0,0 +1,92 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2025 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.render;
+
+import java.util.List;
+
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessible;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.Volatile;
+import net.imglib2.converter.Converter;
+import net.imglib2.type.mask.Masked;
+import net.imglib2.type.operators.SetZero;
+
+/**
+ * {@link VolatileProjector} for a hierarchy of {@link Masked} {@link Volatile} inputs.
+ * After each {@link #map()} call, the projector has a {@link #isValid() state}
+ * that signalizes whether all projected pixels were perfect.
+ *
+ * @author Stephan Saalfeld
+ * @author Tobias Pietzsch
+ */
+class MaskedVolatileHierarchyProjector< A extends Masked< ? extends Volatile< ? > >, B extends SetZero > extends AbstractVolatileHierarchyProjector< A, B >
+{
+ public MaskedVolatileHierarchyProjector(
+ final List< ? extends RandomAccessible< A > > sources,
+ final Converter< ? super A, B > converter,
+ final RandomAccessibleInterval< B > target )
+ {
+ this( sources, converter, target, new byte[ ( int ) ( target.dimension( 0 ) * target.dimension( 1 ) ) ] );
+ }
+
+ public MaskedVolatileHierarchyProjector(
+ final List< ? extends RandomAccessible< A > > sources,
+ final Converter< ? super A, B > converter,
+ final RandomAccessibleInterval< B > target,
+ final byte[] maskArray )
+ {
+ super( sources, converter, target, maskArray );
+ }
+
+ @Override
+ int processLine(final RandomAccess< A > sourceRandomAccess, final RandomAccess< B > targetRandomAccess, final byte resolutionIndex, final int width, final int y )
+ {
+ int numInvalidPixels = 0;
+ final int mi = y * width;
+ for ( int x = 0; x < width; ++x )
+ {
+ if ( mask[ mi + x ] > resolutionIndex )
+ {
+ final A a = sourceRandomAccess.get();
+ final boolean v = a.value().isValid();
+ if ( v )
+ {
+ converter.convert( a, targetRandomAccess.get() );
+ mask[ mi + x ] = resolutionIndex;
+ }
+ else
+ ++numInvalidPixels;
+ }
+ sourceRandomAccess.fwd( 0 );
+ targetRandomAccess.fwd( 0 );
+ }
+ return numInvalidPixels;
+ }
+}
diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
index e3b8cc745..472737885 100644
--- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
+++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
@@ -279,8 +279,6 @@ public class MultiResolutionRenderer
* @param useVolatileIfAvailable
* whether volatile versions of sources should be used if
* available.
- * @param accumulateProjectorFactory
- * can be used to customize how sources are combined.
* @param cacheControl
* the cache controls IO budgeting and fetcher queue.
*/
@@ -292,7 +290,6 @@ public MultiResolutionRenderer(
final int numRenderingThreads,
final ExecutorService renderingExecutorService,
final boolean useVolatileIfAvailable,
- final AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory,
final CacheControl cacheControl )
{
this.display = display;
@@ -324,8 +321,7 @@ public MultiResolutionRenderer(
projectorFactory = new ProjectorFactory(
numRenderingThreads,
renderingForkJoinPool,
- useVolatileIfAvailable,
- accumulateProjectorFactory );
+ useVolatileIfAvailable );
}
/**
diff --git a/src/main/java/bdv/viewer/render/ProjectorFactory.java b/src/main/java/bdv/viewer/render/ProjectorFactory.java
index 06cf75b01..320402d05 100644
--- a/src/main/java/bdv/viewer/render/ProjectorFactory.java
+++ b/src/main/java/bdv/viewer/render/ProjectorFactory.java
@@ -6,13 +6,13 @@
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -40,8 +40,10 @@
import net.imglib2.Volatile;
import net.imglib2.cache.volatiles.CacheHints;
import net.imglib2.cache.volatiles.LoadingStrategy;
+import net.imglib2.display.MaskedToARGBConverter;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.realtransform.RealViews;
+import net.imglib2.type.mask.Masked;
import net.imglib2.type.numeric.ARGBType;
import bdv.img.cache.VolatileCachedCellImg;
@@ -50,6 +52,7 @@
import bdv.viewer.Source;
import bdv.viewer.SourceAndConverter;
import bdv.viewer.ViewerState;
+import net.imglib2.util.Cast;
import net.imglib2.view.Views;
/**
@@ -75,13 +78,6 @@ class ProjectorFactory
*/
private final boolean useVolatileIfAvailable;
- /**
- * Constructs projector that combines rendere ARGB images for individual
- * sources to the final screen image. This can be used to customize how
- * sources are combined.
- */
- private final AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory;
-
/**
* Whether repainting should be triggered after the previously
* {@link #createProjector constructed} projector returns an incomplete
@@ -115,26 +111,22 @@ class ProjectorFactory
/**
* @param numRenderingThreads
- * How many threads to use for rendering.
+ * How many threads to use for rendering.
* @param renderingExecutorService
- * if non-null, this is used for rendering. Note, that it is still
- * important to supply the numRenderingThreads parameter, because that
- * is used to determine into how many sub-tasks rendering is split.
+ * if non-null, this is used for rendering. Note, that it is still
+ * important to supply the numRenderingThreads parameter, because that
+ * is used to determine into how many sub-tasks rendering is split.
* @param useVolatileIfAvailable
- * whether volatile versions of sources should be used if available.
- * @param accumulateProjectorFactory
- * can be used to customize how sources are combined.
+ * whether volatile versions of sources should be used if available.
*/
public ProjectorFactory(
final int numRenderingThreads,
final ExecutorService renderingExecutorService,
- final boolean useVolatileIfAvailable,
- final AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory )
+ final boolean useVolatileIfAvailable )
{
this.numRenderingThreads = numRenderingThreads;
this.renderingExecutorService = renderingExecutorService;
this.useVolatileIfAvailable = useVolatileIfAvailable;
- this.accumulateProjectorFactory = accumulateProjectorFactory;
}
/**
@@ -144,6 +136,20 @@ public ProjectorFactory(
* timepoint of the {@code ViewerState}, and the specified
* {@code screenTransform} from global coordinates to coordinates in the
* {@code screenImage}.
+ *
+ * @param viewerState
+ * the ViewerState to render
+ * @param visibleSourcesOnScreen
+ * @param accumulateProjectorFactory
+ * constructs a projector that combines the rendered ARGB images for individual sources into the final screen image.
+ * @param screenImage
+ * the ARGB screen image to render to
+ * @param screenTransform
+ * transforms global to screenImage coordinates
+ * @param renderStorage
+ * temporary storage
+ *
+ * @return a new projector
*/
public VolatileProjector createProjector(
final ViewerState viewerState,
@@ -161,13 +167,16 @@ public VolatileProjector createProjector(
final int width = ( int ) screenImage.dimension( 0 );
final int height = ( int ) screenImage.dimension( 1 );
+ final AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory = viewerState.getAccumulateProjectorFactory();
+ final boolean useAlphaMaskedSources = accumulateProjectorFactory.requiresMaskedSources();
+
VolatileProjector projector;
if ( visibleSourcesOnScreen.isEmpty() )
projector = new EmptyProjector<>( screenImage );
else if ( visibleSourcesOnScreen.size() == 1 )
{
final byte[] maskArray = renderStorage.getMaskArray( 0 );
- projector = createSingleSourceProjector( viewerState, visibleSourcesOnScreen.get( 0 ), screenImage, screenTransform, maskArray );
+ projector = createSingleSourceProjector( viewerState, visibleSourcesOnScreen.get( 0 ), useAlphaMaskedSources, screenImage, screenTransform, maskArray );
}
else
{
@@ -183,7 +192,7 @@ else if ( visibleSourcesOnScreen.size() == 1 )
++j;
final AffineTransform3D renderTransform = screenTransform.copy();
renderTransform.translate( -offsetX, -offsetY, 0 );
- final VolatileProjector p = createSingleSourceProjector( viewerState, source, renderImage, renderTransform, maskArray );
+ final VolatileProjector p = createSingleSourceProjector( viewerState, source, useAlphaMaskedSources,renderImage, renderTransform, maskArray );
sourceProjectors.add( p );
sourceImages.add( renderImage );
}
@@ -192,9 +201,25 @@ else if ( visibleSourcesOnScreen.size() == 1 )
return projector;
}
+ /**
+ * NB. Unfortunately, "mask" refers both to the target mask and the source
+ * alpha mask.
+ *
+ * The target mask records, for every target pixel, the best resolution
+ * level (so far) that has provided a valid value.
+ *
+ * The {@code useAlphaMaskedSources} determines whether {@link
+ * Source#getMaskedSource masked} versions of sources should be used for
+ * rendering (to provide accurate alpha values for the final ARGBType
+ * accumulation).
+ *
+ * @param useAlphaMaskedSources whether to use the {@link Source#getMaskedSource masked} version of the source for rendering
+ * @param maskArray the target mask
+ */
private < T > VolatileProjector createSingleSourceProjector(
final ViewerState viewerState,
final SourceAndConverter< T > source,
+ final boolean useAlphaMaskedSources,
final RandomAccessibleInterval< ARGBType > screenImage,
final AffineTransform3D screenTransform,
final byte[] maskArray )
@@ -202,29 +227,39 @@ private < T > VolatileProjector createSingleSourceProjector(
if ( useVolatileIfAvailable )
{
if ( source.asVolatile() != null )
- return createSingleSourceVolatileProjector( viewerState, source.asVolatile(), screenImage, screenTransform, maskArray );
+ return createSingleSourceVolatileProjector( viewerState, source.asVolatile(), useAlphaMaskedSources, screenImage, screenTransform, maskArray );
else if ( source.getSpimSource().getType() instanceof Volatile )
{
@SuppressWarnings( "unchecked" )
final SourceAndConverter< ? extends Volatile< ? > > vsource = ( SourceAndConverter< ? extends Volatile< ? > > ) source;
- return createSingleSourceVolatileProjector( viewerState, vsource, screenImage, screenTransform, maskArray );
+ return createSingleSourceVolatileProjector( viewerState, vsource, useAlphaMaskedSources, screenImage, screenTransform, maskArray );
}
}
final int bestLevel = getBestMipMapLevel( viewerState, source, screenTransform );
- return new SimpleVolatileProjector<>(
- getTransformedSource( viewerState, source.getSpimSource(), screenTransform, bestLevel, null ),
- source.getConverter(), screenImage );
+
+ if ( useAlphaMaskedSources )
+ {
+ return new SimpleVolatileProjector<>(
+ getTransformedMaskedSource( viewerState, source.getSpimSource(), screenTransform, bestLevel, null ),
+ MaskedToARGBConverter.wrap( source.getConverter() ), screenImage );
+ }
+ else
+ {
+ return new SimpleVolatileProjector<>(
+ getTransformedSource( viewerState, source.getSpimSource(), screenTransform, bestLevel, null ),
+ source.getConverter(), screenImage );
+ }
}
- private < T extends Volatile< ? > > VolatileProjector createSingleSourceVolatileProjector(
+ private < T extends Volatile< ? >, M extends Masked< T > > VolatileProjector createSingleSourceVolatileProjector(
final ViewerState viewerState,
final SourceAndConverter< T > source,
+ final boolean useAlphaMaskedSources,
final RandomAccessibleInterval< ARGBType > screenImage,
final AffineTransform3D screenTransform,
final byte[] maskArray )
{
- final ArrayList< RandomAccessible< T > > renderList = new ArrayList<>();
final Source< T > spimSource = source.getSpimSource();
final int t = viewerState.getCurrentTimepoint();
@@ -244,15 +279,25 @@ else if ( source.getSpimSource().getType() instanceof Volatile )
prefetch( viewerState, spimSource, screenTransform, l.getMipmapLevel(), cacheHints, screenImage );
}
}
-
levels.sort( MipmapOrdering.renderOrderComparator );
- for ( final MipmapOrdering.Level l : levels )
- renderList.add( getTransformedSource( viewerState, spimSource, screenTransform, l.getMipmapLevel(), l.getRenderCacheHints() ) );
if ( hints.renewHintsAfterPaintingOnce() )
newFrameRequest = true;
- return new VolatileHierarchyProjector<>( renderList, source.getConverter(), screenImage, maskArray );
+ if ( useAlphaMaskedSources )
+ {
+ final ArrayList< RandomAccessible< M > > renderList = new ArrayList<>();
+ for ( final MipmapOrdering.Level l : levels )
+ renderList.add( Cast.unchecked(getTransformedMaskedSource( viewerState, spimSource, screenTransform, l.getMipmapLevel(), l.getRenderCacheHints() ) ) );
+ return new MaskedVolatileHierarchyProjector<>( renderList, MaskedToARGBConverter.wrap( source.getConverter() ), screenImage, maskArray );
+ }
+ else
+ {
+ final ArrayList< RandomAccessible< T > > renderList = new ArrayList<>();
+ for ( final MipmapOrdering.Level l : levels )
+ renderList.add( getTransformedSource( viewerState, spimSource, screenTransform, l.getMipmapLevel(), l.getRenderCacheHints() ) );
+ return new VolatileHierarchyProjector<>( renderList, source.getConverter(), screenImage, maskArray );
+ }
}
/**
@@ -280,21 +325,48 @@ private static < T > RandomAccessible< T > getTransformedSource(
final CacheHints cacheHints )
{
final int timepoint = viewerState.getCurrentTimepoint();
+ applyCacheHints( source, timepoint, mipmapIndex, cacheHints );
+ final RealRandomAccessible< T > ipimg = source.getInterpolatedSource( timepoint, mipmapIndex, viewerState.getInterpolation() );
+ return transformToScreen( source, screenTransform, timepoint, mipmapIndex, ipimg );
+ }
- final RandomAccessibleInterval< T > img = source.getSource( timepoint, mipmapIndex );
- if ( img instanceof VolatileCachedCellImg )
- ( ( VolatileCachedCellImg< ?, ? > ) img ).setCacheHints( cacheHints );
-
- final Interpolation interpolation = viewerState.getInterpolation();
- final RealRandomAccessible< T > ipimg = source.getInterpolatedSource( timepoint, mipmapIndex, interpolation );
+ private static < T > RandomAccessible< ? extends Masked< T > > getTransformedMaskedSource(
+ final ViewerState viewerState,
+ final Source< T > source,
+ final AffineTransform3D screenTransform,
+ final int mipmapIndex,
+ final CacheHints cacheHints )
+ {
+ final int timepoint = viewerState.getCurrentTimepoint();
+ applyCacheHints( source, timepoint, mipmapIndex, cacheHints );
+ final RealRandomAccessible< ? extends Masked< T > > ipimg = source.getInterpolatedMaskedSource( timepoint, mipmapIndex, viewerState.getInterpolation() );
+ return transformToScreen( source, screenTransform, timepoint, mipmapIndex, ipimg );
+ }
+ private static < T > RandomAccessible< T > transformToScreen(
+ final Source< ? > source,
+ final AffineTransform3D screenTransform,
+ final int timepoint,
+ final int mipmapIndex,
+ final RealRandomAccessible< T > ipimg )
+ {
final AffineTransform3D sourceToScreen = new AffineTransform3D();
source.getSourceTransform( timepoint, mipmapIndex, sourceToScreen );
sourceToScreen.preConcatenate( screenTransform );
-
return RealViews.affine( ipimg, sourceToScreen );
}
+ private static void applyCacheHints(
+ final Source< ? > source,
+ final int timepoint,
+ final int mipmapIndex,
+ final CacheHints cacheHints )
+ {
+ final RandomAccessibleInterval< ? > img = source.getSource( timepoint, mipmapIndex );
+ if ( img instanceof VolatileCachedCellImg )
+ ( ( VolatileCachedCellImg< ?, ? > ) img ).setCacheHints( cacheHints );
+ }
+
private static < T > void prefetch(
final ViewerState viewerState,
final Source< T > source,
diff --git a/src/main/java/bdv/viewer/render/TiledProjector.java b/src/main/java/bdv/viewer/render/TiledProjector.java
index 652026c36..edbe1cb33 100644
--- a/src/main/java/bdv/viewer/render/TiledProjector.java
+++ b/src/main/java/bdv/viewer/render/TiledProjector.java
@@ -28,14 +28,10 @@
*/
package bdv.viewer.render;
-import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
-import net.imglib2.parallel.Parallelization;
+
import net.imglib2.util.StopWatch;
/**
diff --git a/src/main/java/bdv/viewer/render/VolatileHierarchyProjector.java b/src/main/java/bdv/viewer/render/VolatileHierarchyProjector.java
index 5e939efca..8b0106160 100644
--- a/src/main/java/bdv/viewer/render/VolatileHierarchyProjector.java
+++ b/src/main/java/bdv/viewer/render/VolatileHierarchyProjector.java
@@ -28,105 +28,29 @@
*/
package bdv.viewer.render;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import net.imglib2.FinalInterval;
+
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessible;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.Volatile;
-import net.imglib2.cache.iotiming.CacheIoTiming;
-import net.imglib2.cache.iotiming.IoStatistics;
import net.imglib2.converter.Converter;
import net.imglib2.type.operators.SetZero;
-import net.imglib2.util.Intervals;
-import net.imglib2.util.StopWatch;
-import net.imglib2.view.Views;
/**
- * {@link VolatileProjector} for a hierarchy of {@link Volatile} inputs. After each
- * {@link #map()} call, the projector has a {@link #isValid() state} that
- * signalizes whether all projected pixels were perfect.
+ * {@link VolatileProjector} for a hierarchy of {@link Volatile} inputs.
+ * After each {@link #map()} call, the projector has a {@link #isValid() state}
+ * that signalizes whether all projected pixels were perfect.
*
* @author Stephan Saalfeld
* @author Tobias Pietzsch
*/
-public class VolatileHierarchyProjector< A extends Volatile< ? >, B extends SetZero > implements VolatileProjector
+public class VolatileHierarchyProjector< A extends Volatile< ? >, B extends SetZero > extends AbstractVolatileHierarchyProjector< A, B >
{
- /**
- * A converter from the source pixel type to the target pixel type.
- */
- private final Converter< ? super A, B > converter;
-
- /**
- * The target interval. Pixels of the target interval should be set by
- * {@link #map}
- */
- private final RandomAccessibleInterval< B > target;
-
- /**
- * List of source resolutions starting with the optimal resolution at index
- * 0. During each {@link #map(boolean)}, for every pixel, resolution levels
- * are successively queried until a valid pixel is found.
- */
- private final List< RandomAccessible< A > > sources;
-
- /**
- * Records, for every target pixel, the best (smallest index) source
- * resolution level that has provided a valid value. Only better (lower
- * index) resolutions are re-tried in successive {@link #map(boolean)}
- * calls.
- */
- private final byte[] mask;
-
- /**
- * {@code true} iff all target pixels were rendered with valid data from the
- * optimal resolution level (level {@code 0}).
- */
- private volatile boolean valid = false;
-
- /**
- * How many levels (starting from level {@code 0}) have to be re-rendered in
- * the next rendering pass, i.e., {@code map()} call.
- */
- private int numInvalidLevels;
-
- /**
- * Source interval which will be used for rendering. This is the 2D target
- * interval expanded to source dimensionality (usually 3D) with
- * {@code min=max=0} in the additional dimensions.
- */
- private final FinalInterval sourceInterval;
-
- /**
- * Time needed for rendering the last frame, in nano-seconds.
- * This does not include time spent in blocking IO.
- */
- private long lastFrameRenderNanoTime;
-
- /**
- * Time spent in blocking IO rendering the last frame, in nano-seconds.
- */
- private long lastFrameIoNanoTime; // TODO move to derived implementation for local sources only
-
- /**
- * temporary variable to store the number of invalid pixels in the current
- * rendering pass.
- */
- private int numInvalidPixels;
-
- /**
- * Flag to indicate that someone is trying to {@link #cancel()} rendering.
- */
- private volatile boolean canceled = false;
-
public VolatileHierarchyProjector(
final List< ? extends RandomAccessible< A > > sources,
final Converter< ? super A, B > converter,
- final RandomAccessibleInterval< B > target)
+ final RandomAccessibleInterval< B > target )
{
this( sources, converter, target, new byte[ ( int ) ( target.dimension( 0 ) * target.dimension( 1 ) ) ] );
}
@@ -135,175 +59,33 @@ public VolatileHierarchyProjector(
final List< ? extends RandomAccessible< A > > sources,
final Converter< ? super A, B > converter,
final RandomAccessibleInterval< B > target,
- final byte[] maskArray)
- {
- this.converter = converter;
- this.target = target;
- this.sources = new ArrayList<>( sources );
- numInvalidLevels = sources.size();
- mask = maskArray;
-
- final int n = Math.max( 2, sources.get( 0 ).numDimensions() );
- final long[] min = new long[ n ];
- final long[] max = new long[ n ];
- min[ 0 ] = target.min( 0 );
- max[ 0 ] = target.max( 0 );
- min[ 1 ] = target.min( 1 );
- max[ 1 ] = target.max( 1 );
- sourceInterval = new FinalInterval( min, max );
-
- lastFrameRenderNanoTime = -1;
- clearMask();
- }
-
- @Override
- public void cancel()
- {
- canceled = true;
- }
-
- @Override
- public long getLastFrameRenderNanoTime()
- {
- return lastFrameRenderNanoTime;
- }
-
- public long getLastFrameIoNanoTime()
- {
- return lastFrameIoNanoTime;
- }
-
- @Override
- public boolean isValid()
+ final byte[] maskArray )
{
- return valid;
- }
-
- /**
- * Set all pixels in target to 100% transparent zero, and mask to all
- * Integer.MAX_VALUE.
- */
- public void clearMask()
- {
- final int size = ( int ) Intervals.numElements( target );
- Arrays.fill( mask, 0, size, Byte.MAX_VALUE );
- numInvalidLevels = sources.size();
- }
-
- /**
- * Clear target pixels that were never written.
- */
- private void clearUntouchedTargetPixels()
- {
- int i = 0;
- for ( final B t : Views.flatIterable( target ) )
- if ( mask[ i++ ] == Byte.MAX_VALUE )
- t.setZero();
+ super( sources, converter, target, maskArray );
}
@Override
- public boolean map( final boolean clearUntouchedTargetPixels )
- {
- if ( canceled )
- return false;
-
- valid = false;
-
- final StopWatch stopWatch = StopWatch.createAndStart();
- final IoStatistics iostat = CacheIoTiming.getIoStatistics();
- final long startTimeIo = iostat.getIoNanoTime();
-
- /*
- * After the for loop, resolutionLevel is the highest (coarsest)
- * resolution for which all pixels could be filled from valid data. This
- * means that in the next pass, i.e., map() call, levels up to
- * resolutionLevel have to be re-rendered.
- */
- int resolutionLevel;
- for ( resolutionLevel = 0; resolutionLevel < numInvalidLevels; ++resolutionLevel )
- {
- numInvalidPixels = 0;
- map( ( byte ) resolutionLevel);
- if ( canceled )
- return false;
- if ( numInvalidPixels == 0 )
- // if this pass was all valid
- numInvalidLevels = resolutionLevel;
- }
-
- if ( clearUntouchedTargetPixels && numInvalidPixels != 0 && !canceled )
- clearUntouchedTargetPixels();
-
- lastFrameIoNanoTime = iostat.getIoNanoTime() - startTimeIo;
- final long lastFrameTime = stopWatch.nanoTime();
-
- // TODO (FORKJOIN): This is inaccurate now, should only use the io time used by current thread
- // --> requires additional API in IoStatistics (imglib2-cache)
- lastFrameRenderNanoTime = lastFrameTime - lastFrameIoNanoTime;
-
- valid = numInvalidLevels == 0;
-
- return !canceled;
- }
-
- /**
- * Copy lines from {@code y = startHeight} up to {@code endHeight}
- * (exclusive) from source {@code resolutionIndex} to target. Check after
- * each line whether rendering was {@link #cancel() canceled}.
- *
- * Only valid source pixels with a current mask value
- * {@code mask>resolutionIndex} are copied to target, and their mask value
- * is set to {@code mask=resolutionIndex}. Invalid source pixels are
- * ignored. Pixels with {@code mask<=resolutionIndex} are ignored, because
- * they have already been written to target during a previous pass.
- *
- *
- * @param resolutionIndex
- * index of source resolution level
- */
- private void map( final byte resolutionIndex )
+ int processLine(final RandomAccess< A > sourceRandomAccess, final RandomAccess< B > targetRandomAccess, final byte resolutionIndex, final int width, final int y )
{
- if ( canceled )
- return;
-
- final RandomAccess< B > targetRandomAccess = target.randomAccess( target );
- final RandomAccess< A > sourceRandomAccess = sources.get( resolutionIndex ).randomAccess( sourceInterval );
- final int width = ( int ) target.dimension( 0 );
- final int height = ( int ) target.dimension( 1 );
- final long[] smin = Intervals.minAsLongArray( sourceInterval );
- int myNumInvalidPixels = 0;
-
- for ( int y = 0; y < height; ++y )
+ int numInvalidPixels = 0;
+ final int mi = y * width;
+ for ( int x = 0; x < width; ++x )
{
- // TODO (FORKJOIN) With tiles being granular enough, probably
- // projectors shouldn't check after each line for cancellation
- // (maybe not at all).
- if ( canceled )
- return;
-
- sourceRandomAccess.setPosition( smin );
- targetRandomAccess.setPosition( smin );
- final int mi = y * width;
- for ( int x = 0; x < width; ++x )
+ if ( mask[ mi + x ] > resolutionIndex )
{
- if ( mask[ mi + x ] > resolutionIndex )
+ final A a = sourceRandomAccess.get();
+ final boolean v = a.isValid();
+ if ( v )
{
- final A a = sourceRandomAccess.get();
- final boolean v = a.isValid();
- if ( v )
- {
- converter.convert( a, targetRandomAccess.get() );
- mask[ mi + x ] = resolutionIndex;
- }
- else
- ++myNumInvalidPixels;
+ converter.convert( a, targetRandomAccess.get() );
+ mask[ mi + x ] = resolutionIndex;
}
- sourceRandomAccess.fwd( 0 );
- targetRandomAccess.fwd( 0 );
+ else
+ ++numInvalidPixels;
}
- ++smin[ 1 ];
+ sourceRandomAccess.fwd( 0 );
+ targetRandomAccess.fwd( 0 );
}
-
- numInvalidPixels += myNumInvalidPixels;
+ return numInvalidPixels;
}
}
diff --git a/src/main/java/bdv/viewer/render/awt/BufferedImageRenderResult.java b/src/main/java/bdv/viewer/render/awt/BufferedImageRenderResult.java
index 9be5962d8..1a685a735 100644
--- a/src/main/java/bdv/viewer/render/awt/BufferedImageRenderResult.java
+++ b/src/main/java/bdv/viewer/render/awt/BufferedImageRenderResult.java
@@ -67,7 +67,7 @@ public void init( final int width, final int height )
data = new int[ width * height ];
screenImage = new ARGBScreenImage( width, height, data );
- bufferedImage = AWTUtils.getBufferedImage( screenImage );;
+ bufferedImage = AWTUtils.getBufferedImage( screenImage, false );;
}
@Override
diff --git a/src/main/resources/bdv/ui/keymap/default.yaml b/src/main/resources/bdv/ui/keymap/default.yaml
index 475ddedba..54e409876 100644
--- a/src/main/resources/bdv/ui/keymap/default.yaml
+++ b/src/main/resources/bdv/ui/keymap/default.yaml
@@ -71,6 +71,10 @@
action: toggle grouping
contexts: [bdv]
triggers: [G]
+- !mapping
+ action: toggle blending mode
+ contexts: [bdv]
+ triggers: [U]
- !mapping
action: set current source 0
contexts: [bdv]
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg.png b/src/main/resources/bdv/ui/viewermodepanel/blend_avg.png
new file mode 100644
index 000000000..759f03f26
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_avg.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_avg.svg
new file mode 100644
index 000000000..a9501a82e
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_avg.svg
@@ -0,0 +1,24 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_200.png
new file mode 100644
index 000000000..96d58a9e9
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_200.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.png b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.png
new file mode 100644
index 000000000..a1efe60b8
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.svg
new file mode 100644
index 000000000..ba4f8cbdc
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark.svg
@@ -0,0 +1,24 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark_200.png
new file mode 100644
index 000000000..f276257e5
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_avg_dark_200.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom.png b/src/main/resources/bdv/ui/viewermodepanel/blend_custom.png
new file mode 100644
index 000000000..439006bfa
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_custom.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_custom.svg
new file mode 100644
index 000000000..82e8b1568
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_custom.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_200.png
new file mode 100644
index 000000000..3e6b0c643
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_200.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.png b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.png
new file mode 100644
index 000000000..65cd2ebde
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.svg
new file mode 100644
index 000000000..cfa6e53fe
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark_200.png
new file mode 100644
index 000000000..2af7745e7
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_custom_dark_200.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum.png b/src/main/resources/bdv/ui/viewermodepanel/blend_sum.png
new file mode 100644
index 000000000..7a3d21ccf
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_sum.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_sum.svg
new file mode 100644
index 000000000..2e0dd97c8
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_sum.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_200.png
new file mode 100644
index 000000000..fa6446648
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_200.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.png b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.png
new file mode 100644
index 000000000..d5b7ede06
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.png differ
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.svg b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.svg
new file mode 100644
index 000000000..b86938703
--- /dev/null
+++ b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark_200.png b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark_200.png
new file mode 100644
index 000000000..575eb06ce
Binary files /dev/null and b/src/main/resources/bdv/ui/viewermodepanel/blend_sum_dark_200.png differ
diff --git a/src/test/java/bdv/ui/CreateViewerState.java b/src/test/java/bdv/ui/CreateViewerState.java
index bac63ea00..4ea54fde7 100644
--- a/src/test/java/bdv/ui/CreateViewerState.java
+++ b/src/test/java/bdv/ui/CreateViewerState.java
@@ -39,6 +39,7 @@
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealRandomAccessible;
import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.mask.Masked;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
@@ -136,6 +137,18 @@ public RealRandomAccessible< UnsignedShortType > getInterpolatedSource( final in
return null;
}
+ @Override
+ public RandomAccessibleInterval< ? extends Masked< UnsignedShortType > > getMaskedSource( final int t, final int level )
+ {
+ return null;
+ }
+
+ @Override
+ public RealRandomAccessible< ? extends Masked< UnsignedShortType > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method )
+ {
+ return null;
+ }
+
@Override
public void getSourceTransform( final int t, final int level, final AffineTransform3D transform )
{
diff --git a/src/test/java/bdv/util/benchmark/RenderingSetup.java b/src/test/java/bdv/util/benchmark/RenderingSetup.java
index 01028df2a..02f624ee9 100644
--- a/src/test/java/bdv/util/benchmark/RenderingSetup.java
+++ b/src/test/java/bdv/util/benchmark/RenderingSetup.java
@@ -149,11 +149,10 @@ public static class Renderer
public Renderer(final int[] targetSize, final int numRenderingThreads)
{
target = new BenchmarkRenderTarget( targetSize[ 0 ], targetSize[ 1 ] );
- final AccumulateProjectorFactory< ARGBType > accumulateProjectorFactory = AccumulateProjectorARGB.factory;
renderer = new MultiResolutionRenderer(
target, () -> {}, new double[] { 1 }, 0,
numRenderingThreads, null, false,
- accumulateProjectorFactory, new CacheControl.Dummy() );
+ new CacheControl.Dummy() );
}
public void render( final ViewerState state )
diff --git a/src/test/java/bdv/viewer/BasicViewerStateTest.java b/src/test/java/bdv/viewer/BasicViewerStateTest.java
index df1c6a0f2..b23c6f8f0 100644
--- a/src/test/java/bdv/viewer/BasicViewerStateTest.java
+++ b/src/test/java/bdv/viewer/BasicViewerStateTest.java
@@ -40,6 +40,8 @@
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealRandomAccessible;
import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.mask.Masked;
+
import org.junit.Assert;
import org.junit.Test;
@@ -397,6 +399,18 @@ public RealRandomAccessible< Void > getInterpolatedSource( final int t, final in
return null;
}
+ @Override
+ public RandomAccessibleInterval< ? extends Masked< Void > > getMaskedSource( final int t, final int level )
+ {
+ return null;
+ }
+
+ @Override
+ public RealRandomAccessible< ? extends Masked< Void > > getInterpolatedMaskedSource( final int t, final int level, final Interpolation method )
+ {
+ return null;
+ }
+
@Override
public void getSourceTransform( final int t, final int level, final AffineTransform3D transform )
{