diff --git a/include/GafferImage/Sampler.h b/include/GafferImage/Sampler.h index 2111ea06c9a..a758c6289de 100644 --- a/include/GafferImage/Sampler.h +++ b/include/GafferImage/Sampler.h @@ -95,6 +95,13 @@ class GAFFERIMAGE_API Sampler /// 0.5, 0.5. float sample( float x, float y ); + /// Call a functor for all pixels in the region. + /// The signature of the functor must be `F( float value, int x, int y )`. + /// The order is not necessarily scanline order, but it is guaranteed to be + /// deterministic and visit every pixel exactly once. + template + void visitPixels( Imath::Box2i region, F &&lambda ); + /// Appends a hash that represent all the pixel /// values within the requested sample area. void hash( IECore::MurmurHash &h ) const; diff --git a/include/GafferImage/Sampler.inl b/include/GafferImage/Sampler.inl index dd8bc940d30..0d7dd5eebf0 100644 --- a/include/GafferImage/Sampler.inl +++ b/include/GafferImage/Sampler.inl @@ -94,6 +94,103 @@ inline float Sampler::sample( float x, float y ) return OIIO::bilerp( x0y0, x1y0, x0y1, x1y1, xf, yf ); } +template +void Sampler::visitPixels( Imath::Box2i region, F &&visitor ) +{ + if( !BufferAlgo::contains( m_dataWindow, region ) ) + { + if( m_boundingMode == Black ) + { + for( int y = region.min.y; y < region.max.y; y++ ) + { + if( y < m_dataWindow.min.y || y >= m_dataWindow.max.y ) + { + for( int x = region.min.x; x < region.max.x; x++ ) + { + visitor( 0.0f, x, y ); + } + } + else + { + for( int x = region.min.x; x < m_dataWindow.min.x; x++ ) + { + visitor( 0.0f, x, y ); + } + + for( int x = m_dataWindow.max.x + 1; x < region.max.x; x++ ) + { + visitor( 0.0f, x, y ); + } + } + } + } + else + { + // If we wanted to optimize this case, we could break the area within + // `region` but outside `m_dataWindow` into 8 octants, based on where we reuse + // the same border values for multiple outside pixels. Would be a potentially + // huge win for large regions with small data windows, but that's a rare case, + // and it would take about 90 lines of fairly fiddly code, it's simpler to do + // something slow instead. + for( int y = region.min.y; y < region.max.y; y++ ) + { + if( y < m_dataWindow.min.y || y >= m_dataWindow.max.y ) + { + for( int x = region.min.x; x < region.max.x; x++ ) + { + visitor( sample( x, y ), x, y ); + } + } + else + { + for( int x = region.min.x; x < m_dataWindow.min.x; x++ ) + { + visitor( sample( x, y ), x, y ); + } + + for( int x = m_dataWindow.max.x + 1; x < region.max.x; x++ ) + { + visitor( sample( x, y ), x, y ); + } + } + } + } + + region = BufferAlgo::intersection( m_dataWindow, region ); + } + + Imath::V2i minTile = ImagePlug::tileIndex( region.min ); + Imath::V2i maxTile = ImagePlug::tileIndex( region.max - Imath::V2i( 1 ) ); + for( int tx = minTile.x; tx <= maxTile.x; tx++ ) + { + for( int ty = minTile.y; ty <= maxTile.y; ty++ ) + { + Imath::V2i tileOrigin = Imath::V2i( tx * ImagePlug::tileSize(), ty * ImagePlug::tileSize() ); + + const float *tileData; + Imath::V2i tileIndex; + cachedData( tileOrigin, tileData, tileIndex ); + for( + int y = std::max( tileOrigin.y, region.min.y ); + y < std::min( tileOrigin.y + ImagePlug::tileSize(), region.max.y ); + y++ + ) + { + for( + int x = std::max( tileOrigin.x, region.min.x ); + x < std::min( tileOrigin.x + ImagePlug::tileSize(), region.max.x ); + x++ + ) + { + visitor( *(tileData + ( y - tileOrigin.y ) * ImagePlug::tileSize() + x - tileOrigin.x), x, y ); + } + } + } + } +} + + + inline void Sampler::cachedData( Imath::V2i p, const float *& tileData, Imath::V2i &tilePixelIndex ) { // Get the smart pointer to the tile we want.