|
| 1 | +package fiji.plugin.trackmate.gui.editor.labkit.component; |
| 2 | + |
| 3 | +import java.util.ArrayList; |
| 4 | +import java.util.List; |
| 5 | +import java.util.concurrent.ExecutorService; |
| 6 | +import java.util.stream.Collectors; |
| 7 | + |
| 8 | +import bdv.viewer.SourceAndConverter; |
| 9 | +import bdv.viewer.render.AccumulateProjectorFactory; |
| 10 | +import bdv.viewer.render.ProjectorUtils; |
| 11 | +import bdv.viewer.render.ProjectorUtils.ArrayData; |
| 12 | +import bdv.viewer.render.VolatileProjector; |
| 13 | +import net.imglib2.Interval; |
| 14 | +import net.imglib2.RandomAccessible; |
| 15 | +import net.imglib2.RandomAccessibleInterval; |
| 16 | +import net.imglib2.type.numeric.ARGBType; |
| 17 | +import net.imglib2.util.Intervals; |
| 18 | +import net.imglib2.util.StopWatch; |
| 19 | + |
| 20 | +public class SrcOverAccumulateProjectorARGB |
| 21 | +{ |
| 22 | + |
| 23 | + public static class Factory implements AccumulateProjectorFactory< ARGBType > |
| 24 | + { |
| 25 | + |
| 26 | + private float[] sourceAlphas; |
| 27 | + |
| 28 | + public Factory( final float[] sourceAlphas ) |
| 29 | + { |
| 30 | + this.sourceAlphas = sourceAlphas; |
| 31 | + } |
| 32 | + |
| 33 | + public void setSourceAlpha( final int sourceIndex, final float alpha ) |
| 34 | + { |
| 35 | + if ( sourceIndex >= 0 && sourceIndex < sourceAlphas.length ) |
| 36 | + { |
| 37 | + this.sourceAlphas[ sourceIndex ] = Math.max( 0, Math.min( 1, alpha ) ); |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + public float getSourceAlpha( final int sourceIndex ) |
| 42 | + { |
| 43 | + return ( sourceIndex >= 0 && sourceIndex < sourceAlphas.length ) |
| 44 | + ? sourceAlphas[ sourceIndex ] |
| 45 | + : 1.0f; |
| 46 | + } |
| 47 | + |
| 48 | + @Override |
| 49 | + public VolatileProjector createProjector( |
| 50 | + final List< VolatileProjector > sourceProjectors, |
| 51 | + final List< SourceAndConverter< ? > > sources, |
| 52 | + final List< ? extends RandomAccessible< ? extends ARGBType > > sourceScreenImages, |
| 53 | + final RandomAccessibleInterval< ARGBType > targetScreenImage, |
| 54 | + final int numThreads, |
| 55 | + final ExecutorService executorService ) |
| 56 | + { |
| 57 | + |
| 58 | + final ProjectorData projectorData = getProjectorData( sourceScreenImages, targetScreenImage ); |
| 59 | + if ( projectorData == null ) |
| 60 | + { |
| 61 | + // Fallback to generic (not optimized) |
| 62 | + return null; |
| 63 | + } |
| 64 | + |
| 65 | + return new SrcOverProjectorARGBArrayData( sourceProjectors, projectorData, sourceAlphas ); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + private static class ProjectorData |
| 70 | + { |
| 71 | + private final ArrayData targetData; |
| 72 | + |
| 73 | + private final List< int[] > sourceData; |
| 74 | + |
| 75 | + ProjectorData( final ArrayData targetData, final List< int[] > sourceData ) |
| 76 | + { |
| 77 | + this.targetData = targetData; |
| 78 | + this.sourceData = sourceData; |
| 79 | + } |
| 80 | + |
| 81 | + ArrayData targetData() |
| 82 | + { |
| 83 | + return targetData; |
| 84 | + } |
| 85 | + |
| 86 | + List< int[] > sourceData() |
| 87 | + { |
| 88 | + return sourceData; |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + private static ProjectorData getProjectorData( |
| 93 | + final List< ? extends RandomAccessible< ? extends ARGBType > > sources, |
| 94 | + final RandomAccessibleInterval< ARGBType > target ) |
| 95 | + { |
| 96 | + |
| 97 | + final ArrayData targetData = ProjectorUtils.getARGBArrayData( target ); |
| 98 | + if ( targetData == null ) |
| 99 | + return null; |
| 100 | + |
| 101 | + final int numSources = sources.size(); |
| 102 | + final List< int[] > sourceData = new ArrayList<>( numSources ); |
| 103 | + for ( int i = 0; i < numSources; ++i ) |
| 104 | + { |
| 105 | + final RandomAccessible< ? extends ARGBType > source = sources.get( i ); |
| 106 | + if ( !( source instanceof RandomAccessibleInterval ) ) |
| 107 | + return null; |
| 108 | + if ( !Intervals.equals( target, ( Interval ) source ) ) |
| 109 | + return null; |
| 110 | + final int[] data = ProjectorUtils.getARGBArrayImgData( source ); |
| 111 | + if ( data == null ) |
| 112 | + return null; |
| 113 | + sourceData.add( data ); |
| 114 | + } |
| 115 | + |
| 116 | + return new ProjectorData( targetData, sourceData ); |
| 117 | + } |
| 118 | + |
| 119 | + private static class SrcOverProjectorARGBArrayData implements VolatileProjector |
| 120 | + { |
| 121 | + |
| 122 | + private List< VolatileProjector > sourceProjectors; |
| 123 | + |
| 124 | + private final List< int[] > sources; |
| 125 | + |
| 126 | + private final ArrayData target; |
| 127 | + |
| 128 | + private final float[] sourceAlphas; |
| 129 | + |
| 130 | + private long lastFrameRenderNanoTime; |
| 131 | + |
| 132 | + private volatile boolean canceled = false; |
| 133 | + |
| 134 | + private volatile boolean valid = false; |
| 135 | + |
| 136 | + public SrcOverProjectorARGBArrayData( |
| 137 | + final List< VolatileProjector > sourceProjectors, |
| 138 | + final ProjectorData projectorData, |
| 139 | + final float[] sourceAlphas ) |
| 140 | + { |
| 141 | + |
| 142 | + this.sourceProjectors = sourceProjectors; |
| 143 | + this.target = projectorData.targetData(); |
| 144 | + this.sources = projectorData.sourceData(); |
| 145 | + this.sourceAlphas = sourceAlphas; |
| 146 | + } |
| 147 | + |
| 148 | + @Override |
| 149 | + public boolean map( final boolean clearUntouchedTargetPixels ) |
| 150 | + { |
| 151 | + if ( canceled ) |
| 152 | + return false; |
| 153 | + |
| 154 | + if ( isValid() ) |
| 155 | + return true; |
| 156 | + |
| 157 | + final StopWatch stopWatch = StopWatch.createAndStart(); |
| 158 | + |
| 159 | + // Render each source |
| 160 | + sourceProjectors.forEach( p -> p.map( clearUntouchedTargetPixels ) ); |
| 161 | + |
| 162 | + if ( canceled ) |
| 163 | + return false; |
| 164 | + |
| 165 | + // Composite with SRC_OVER |
| 166 | + mapAccumulate(); |
| 167 | + |
| 168 | + sourceProjectors = sourceProjectors.stream() |
| 169 | + .filter( p -> !p.isValid() ) |
| 170 | + .collect( Collectors.toList() ); |
| 171 | + |
| 172 | + lastFrameRenderNanoTime = stopWatch.nanoTime(); |
| 173 | + valid = sourceProjectors.isEmpty(); |
| 174 | + return !canceled; |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Accumulate pixels of all sources to target using SRC_OVER blending. |
| 179 | + */ |
| 180 | + private void mapAccumulate() |
| 181 | + { |
| 182 | + if ( canceled ) |
| 183 | + return; |
| 184 | + |
| 185 | + final int numSources = sources.size(); |
| 186 | + final int width = target.width(); |
| 187 | + final int height = target.height(); |
| 188 | + final int[] targetData = target.data(); |
| 189 | + final int targetStride = target.stride(); |
| 190 | + final int targetOx = target.ox(); |
| 191 | + final int targetOy = target.oy(); |
| 192 | + |
| 193 | + // Process each row |
| 194 | + for ( int y = 0; y < height; ++y ) |
| 195 | + { |
| 196 | + final int oTarget = ( y + targetOy ) * targetStride + targetOx; |
| 197 | + final int oSource = y * width; |
| 198 | + |
| 199 | + // Process each pixel in the row |
| 200 | + for ( int x = 0; x < width; ++x ) |
| 201 | + { |
| 202 | + int resultARGB = 0; |
| 203 | + |
| 204 | + // Composite each source front-to-back |
| 205 | + for ( int s = 0; s < numSources; ++s ) |
| 206 | + { |
| 207 | + final int[] source = sources.get( s ); |
| 208 | + final int sourceARGB = source[ oSource + x ]; |
| 209 | + |
| 210 | + // Get global alpha for this source |
| 211 | + final float globalAlpha = ( s < sourceAlphas.length ) |
| 212 | + ? sourceAlphas[ s ] |
| 213 | + : 1.0f; |
| 214 | + |
| 215 | + // Extract source components |
| 216 | + int srcA = ARGBType.alpha( sourceARGB ); |
| 217 | + final int srcR = ARGBType.red( sourceARGB ); |
| 218 | + final int srcG = ARGBType.green( sourceARGB ); |
| 219 | + final int srcB = ARGBType.blue( sourceARGB ); |
| 220 | + |
| 221 | + // Apply global alpha to per-pixel alpha |
| 222 | + srcA = ( int ) ( srcA * globalAlpha ); |
| 223 | + |
| 224 | + // SRC_OVER blending |
| 225 | + if ( s == 0 || resultARGB == 0 ) |
| 226 | + { |
| 227 | + // First layer or nothing underneath |
| 228 | + resultARGB = ARGBType.rgba( srcR, srcG, srcB, srcA ); |
| 229 | + } |
| 230 | + else |
| 231 | + { |
| 232 | + // SRC_OVER: C_out = C_src + C_dst * (1 - alpha_src) |
| 233 | + final int dstA = ARGBType.alpha( resultARGB ); |
| 234 | + final int dstR = ARGBType.red( resultARGB ); |
| 235 | + final int dstG = ARGBType.green( resultARGB ); |
| 236 | + final int dstB = ARGBType.blue( resultARGB ); |
| 237 | + |
| 238 | + final float srcAlphaF = srcA / 255.0f; |
| 239 | + final float oneMinusSrcAlpha = 1.0f - srcAlphaF; |
| 240 | + |
| 241 | + final int outR = ( int ) ( srcR * srcAlphaF + dstR * oneMinusSrcAlpha ); |
| 242 | + final int outG = ( int ) ( srcG * srcAlphaF + dstG * oneMinusSrcAlpha ); |
| 243 | + final int outB = ( int ) ( srcB * srcAlphaF + dstB * oneMinusSrcAlpha ); |
| 244 | + final int outA = ( int ) ( srcA + dstA * oneMinusSrcAlpha ); |
| 245 | + |
| 246 | + resultARGB = ARGBType.rgba( outR, outG, outB, Math.min( 255, outA ) ); |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + targetData[ oTarget + x ] = resultARGB; |
| 251 | + } |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + @Override |
| 256 | + public void cancel() |
| 257 | + { |
| 258 | + canceled = true; |
| 259 | + for ( final VolatileProjector p : sourceProjectors ) |
| 260 | + p.cancel(); |
| 261 | + } |
| 262 | + |
| 263 | + @Override |
| 264 | + public long getLastFrameRenderNanoTime() |
| 265 | + { |
| 266 | + return lastFrameRenderNanoTime; |
| 267 | + } |
| 268 | + |
| 269 | + @Override |
| 270 | + public boolean isValid() |
| 271 | + { |
| 272 | + return valid; |
| 273 | + } |
| 274 | + } |
| 275 | +} |
0 commit comments