text: ##########################################################################
  #
  #  Copyright (c) 2013-2015, Image Engine Design Inc. All rights reserved.
  #
  #  Redistribution and use in source and binary forms, with or without
  #  modification, are permitted provided that the following conditions are
  #  met:
  #
  #      * Redistributions of source code must retain the above
  #        copyright notice, this list of conditions and the following
  #        disclaimer.
  #
  #      * 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.
  #
  #      * Neither the name of Image Engine Design nor the names of
  #        any other contributors to this software may be used to endorse or
  #        promote products derived from this software without specific prior
  #        written permission.
  #
  #  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 OWNER 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.
  #
  ##########################################################################
  
  import os
  import unittest
  import imath
  
  import IECore
  
  import Gaffer
  import GafferImage
  import GafferImageTest
  
  class SamplerTest( GafferImageTest.ImageTestCase ) :
  
  	fileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/checker.exr" )
  
  	def testOutOfBoundsSampleModeBlack( self ) :
  
  		c = GafferImage.Constant()
  		c["color"].setValue( imath.Color4f( 1 ) )
  
  		dw = c["out"]["dataWindow"].getValue();
  		s = GafferImage.Sampler( c["out"], "R", dw, GafferImage.Sampler.BoundingMode.Black )
  
  		# Check integer sampling.
  		#########################
  
  		# Pixels on corners of dataWindow should be white.
  
  		self.assertEqual( s.sample( dw.min().x, dw.min().y ), 1 )
  		self.assertEqual( s.sample( dw.max().x - 1, dw.min().y ), 1 )
  		self.assertEqual( s.sample( dw.max().x - 1, dw.max().y - 1 ), 1 )
  		self.assertEqual( s.sample( dw.min().x, dw.max().y - 1 ), 1 )
  
  		# Pixels just outside dataWindow should be white.
  
  		self.assertEqual( s.sample( dw.min().x - 1, dw.min().y - 1 ), 0 )
  		self.assertEqual( s.sample( dw.max().x, dw.min().y - 1 ), 0 )
  		self.assertEqual( s.sample( dw.max().x, dw.max().y ), 0 )
  		self.assertEqual( s.sample( dw.min().x - 1, dw.max().y ), 0 )
  
  		# Check interpolated sampling.
  		##############################
  
  		# Pixels on corners of dataWindow should be interpolating
  		# to black. Note that here we're sampling at the corners
  		# of pixels, not centers.
  
  		self.assertEqual( s.sample( float( dw.min().x ), float( dw.min().y ) ), 0.25 )
  		self.assertEqual( s.sample( float( dw.max().x ), float( dw.min().y ) ), 0.25 )
  		self.assertEqual( s.sample( float( dw.max().x ), float( dw.max().y ) ), 0.25 )
  		self.assertEqual( s.sample( float( dw.min().x ), float( dw.max().y ) ), 0.25 )
  
  		# Pixel centers at the corners of dataWindow should be white.
  
  		self.assertEqual( s.sample( float( dw.min().x + 0.5 ), float( dw.min().y + 0.5 ) ), 1 )
  		self.assertEqual( s.sample( float( dw.max().x - 0.5 ), float( dw.min().y + 0.5 ) ), 1 )
  		self.assertEqual( s.sample( float( dw.max().x - 0.5 ), float( dw.max().y - 0.5 ) ), 1 )
  		self.assertEqual( s.sample( float( dw.min().x + 0.5 ), float( dw.max().y - 0.5 ) ), 1 )
  
  	def testOutOfBoundsSampleModeClamp( self ) :
  
  		r = GafferImage.ImageReader()
  		r["fileName"].setValue( self.fileName )
  
  		dw = r["out"]["dataWindow"].getValue();
  		s = GafferImage.Sampler( r["out"], "R", dw, GafferImage.Sampler.BoundingMode.Clamp )
  
  		# Get the exact values of the corner pixels.
  		bl = s.sample( dw.min().x, dw.min().y )
  		br = s.sample( dw.max().x - 1, dw.min().y  )
  		tr = s.sample( dw.max().x - 1, dw.max().y - 1 )
  		tl = s.sample( dw.min().x, dw.max().y - 1 )
  
  		# Sample out of bounds and assert that the same value as the nearest pixel is returned.
  		self.assertEqual( s.sample( dw.min().x-1, dw.min().y ), bl )
  		self.assertEqual( s.sample( dw.min().x, dw.min().y-1 ), bl )
  		self.assertEqual( s.sample( dw.max().x-1, dw.max().y ), tr )
  		self.assertEqual( s.sample( dw.max().x, dw.max().y-1 ), tr )
  		self.assertEqual( s.sample( dw.min().x-1, dw.max().y-1 ), tl )
  		self.assertEqual( s.sample( dw.min().x, dw.max().y ), tl )
  		self.assertEqual( s.sample( dw.max().x, dw.min().y ), br )
  		self.assertEqual( s.sample( dw.max().x-1, dw.min().y-1 ), br )
  
  	def test2x2Checker( self ) :
  
  		reader = GafferImage.ImageReader()
  		reader["fileName"].setValue( os.path.dirname( __file__ ) + "/images/checker2x2.exr" )
  
  		# As long as the sample region includes the valid range of our image, and all
  		# the pixels we're going to request, it should have no effect on our sampling.
  		# So test with a few such ranges.
  		sampleRegions = [
  			imath.Box2i( imath.V2i( 0 ), imath.V2i( GafferImage.ImagePlug.tileSize() ) ),
  			imath.Box2i( -imath.V2i( GafferImage.ImagePlug.tileSize() ), imath.V2i( GafferImage.ImagePlug.tileSize() ) ),
  			imath.Box2i( imath.V2i( -1 ), imath.V2i( 4 ) ),
  		]
  
  		# List of positions inside and outside of the image, along
  		# with expected values if outside points are clamped inside.
  		samples = [
  			( imath.V2i( 0, 0 ), 1 ),
  			( imath.V2i( 1, 0 ), 0 ),
  			( imath.V2i( 1, 1 ), 1 ),
  			( imath.V2i( 0, 1 ), 0 ),
  			( imath.V2i( -1, 0 ), 1 ),
  			( imath.V2i( 2, 0 ), 0 ),
  			( imath.V2i( 0, 3 ), 0 ),
  			( imath.V2i( 0, -1 ), 1 ),
  			( imath.V2i( 3, 3 ), 1 ),
  			( imath.V2i( -1, -1 ), 1 ),
  			( imath.V2i( -1, 2 ), 0 ),
  			( imath.V2i( 2, 2 ), 1 ),
  			( imath.V2f( 1, 1 ), 0.5 ),
  		]
  
  		# Assert all is as expected for all combos of region and sample.
  		for region in sampleRegions :
  			sampler = GafferImage.Sampler( reader["out"], "R", region, boundingMode = GafferImage.Sampler.BoundingMode.Clamp )
  			for position, value in samples :
  				self.assertEqual( sampler.sample( position.x, position.y ), value )
  
  	def testSampleOutsideDataWindow( self ) :
  
  		constant = GafferImage.Constant()
  		constant["format"].setValue( GafferImage.Format( 1000, 1000 ) )
  		constant["color"].setValue( imath.Color4f( 1 ) )
  
  		crop = GafferImage.Crop()
  		crop["in"].setInput( constant["out"] )
  		crop["areaSource"].setValue( crop.AreaSource.Area )
  		crop["area"].setValue( imath.Box2i( imath.V2i( 135 ), imath.V2i( 214 ) ) )
  		crop["affectDisplayWindow"].setValue( False )
  
  		sampler = GafferImage.Sampler( crop["out"], "R", imath.Box2i( imath.V2i( 0 ), imath.V2i( 50 ) ), boundingMode = GafferImage.Sampler.BoundingMode.Clamp )
  		self.assertEqual( sampler.sample( 0, 0 ), 1 )
  
  		sampler = GafferImage.Sampler( crop["out"], "R", imath.Box2i( imath.V2i( 0 ), imath.V2i( 50 ) ), boundingMode = GafferImage.Sampler.BoundingMode.Black )
  		self.assertEqual( sampler.sample( 0, 0 ), 0 )
  
  	def testHashIncludesBlackPixels( self ) :
  
  		constant = GafferImage.Constant()
  		constant["format"].setValue( GafferImage.Format( 1000, 1000 ) )
  		constant["color"].setValue( imath.Color4f( 1 ) )
  
  		crop = GafferImage.Crop()
  		crop["in"].setInput( constant["out"] )
  		crop["areaSource"].setValue( crop.AreaSource.Area )
  		crop["area"].setValue( imath.Box2i( imath.V2i( 0 ), imath.V2i( 200 ) ) )
  		crop["affectDisplayWindow"].setValue( False )
  		crop["affectDataWindow"].setValue( False )
  
  		# Samples the whole data window
  		sampler1 = GafferImage.Sampler( crop["out"], "R", imath.Box2i( imath.V2i( 0 ), imath.V2i( 200 ) ), boundingMode = GafferImage.Sampler.BoundingMode.Black )
  		# Samples the whole data window and then some.
  		sampler2 = GafferImage.Sampler( crop["out"], "R", imath.Box2i( imath.V2i( 0 ), imath.V2i( 210 ) ), boundingMode = GafferImage.Sampler.BoundingMode.Black )
  		# Samples the whole data window and then some and then some more.
  		sampler3 = GafferImage.Sampler( crop["out"], "R", imath.Box2i( imath.V2i( 0 ), imath.V2i( 220 ) ), boundingMode = GafferImage.Sampler.BoundingMode.Black )
  
  		# The hashes must take account of the additional pixels being sampled.
  		self.assertNotEqual( sampler1.hash(), sampler2.hash() )
  		self.assertNotEqual( sampler2.hash(), sampler3.hash() )
  		self.assertNotEqual( sampler3.hash(), sampler1.hash() )
  
  	def testClampModeWithEmptyDataWindow( self ) :
  
  		empty = self.emptyImage()
  		sampler = GafferImage.Sampler( empty["out"], "R", empty["out"]["format"].getValue().getDisplayWindow(), boundingMode = GafferImage.Sampler.BoundingMode.Clamp )
  		self.assertEqual( sampler.sample( 0, 0 ), 0.0 )
  
  if __name__ == "__main__":
  	unittest.main()
  