"""Image-handling routines ### Unresolved: Following methods are not yet resolved due to my not being sure how the function should be wrapped: glCompressedTexImage3D glCompressedTexImage2D glCompressedTexImage1D glCompressedTexSubImage3D glCompressedTexSubImage2D glCompressedTexSubImage1D """ from OpenGL.raw.GL.VERSION import GL_1_1,GL_1_2, GL_3_0 from OpenGL import images, arrays, wrapper from OpenGL.arrays import arraydatatype from OpenGL._bytes import bytes,integer_types from OpenGL.raw.GL import _types import ctypes def asInt( value ): if isinstance( value, float ): return int(round(value,0)) return value ## update the image tables with standard image types... images.COMPONENT_COUNTS.update( { GL_1_1.GL_BITMAP : 1, # must be GL_UNSIGNED_BYTE GL_1_1.GL_RED : 1, GL_1_1.GL_GREEN : 1, GL_1_1.GL_BLUE : 1, GL_1_1.GL_ALPHA : 1, GL_3_0.GL_RED_INTEGER : 1, GL_3_0.GL_GREEN_INTEGER : 1, GL_3_0.GL_BLUE_INTEGER : 1, GL_3_0.GL_ALPHA_INTEGER : 1, GL_1_1.GL_LUMINANCE : 1, GL_1_1.GL_LUMINANCE_ALPHA : 2, GL_1_1.GL_COLOR_INDEX : 1, GL_1_1.GL_STENCIL_INDEX : 1, GL_1_1.GL_DEPTH_COMPONENT : 1, GL_1_1.GL_RGB : 3, GL_1_2.GL_BGR : 3, GL_3_0.GL_RGB16F : 3, GL_3_0.GL_RGB16I : 3, GL_3_0.GL_RGB16UI : 3, GL_3_0.GL_RGB32F : 3, GL_3_0.GL_RGB32I : 3, GL_3_0.GL_RGB32UI : 3, GL_3_0.GL_RGB8I : 3, GL_3_0.GL_RGB8UI : 3, GL_3_0.GL_RGB9_E5 : 3, GL_3_0.GL_RGB_INTEGER : 3, GL_1_1.GL_RGBA : 4, GL_1_2.GL_BGRA : 4, GL_3_0.GL_RGBA16F : 4, GL_3_0.GL_RGBA16I : 4, GL_3_0.GL_RGBA16UI : 4, GL_3_0.GL_RGBA32F : 4, GL_3_0.GL_RGBA32I : 4, GL_3_0.GL_RGBA32UI : 4, GL_3_0.GL_RGBA8I : 4, GL_3_0.GL_RGBA8UI : 4, GL_3_0.GL_RGBA_INTEGER : 4, } ) images.TYPE_TO_ARRAYTYPE.update( { GL_3_0.GL_HALF_FLOAT : GL_3_0.GL_HALF_FLOAT, GL_1_2.GL_UNSIGNED_BYTE_3_3_2 : GL_1_1.GL_UNSIGNED_BYTE, GL_1_2.GL_UNSIGNED_BYTE_2_3_3_REV : GL_1_1.GL_UNSIGNED_BYTE, GL_1_2.GL_UNSIGNED_SHORT_4_4_4_4 : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_SHORT_4_4_4_4_REV : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_SHORT_5_5_5_1 : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_SHORT_1_5_5_5_REV : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_SHORT_5_6_5 : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_SHORT_5_6_5_REV : GL_1_1.GL_UNSIGNED_SHORT, GL_1_2.GL_UNSIGNED_INT_8_8_8_8 : GL_1_1.GL_UNSIGNED_INT, GL_1_2.GL_UNSIGNED_INT_8_8_8_8_REV : GL_1_1.GL_UNSIGNED_INT, GL_1_2.GL_UNSIGNED_INT_10_10_10_2 : GL_1_1.GL_UNSIGNED_INT, GL_1_2.GL_UNSIGNED_INT_2_10_10_10_REV : GL_1_1.GL_UNSIGNED_INT, GL_1_1.GL_UNSIGNED_BYTE : GL_1_1.GL_UNSIGNED_BYTE, GL_1_1.GL_BYTE: GL_1_1.GL_BYTE, GL_1_1.GL_UNSIGNED_SHORT : GL_1_1.GL_UNSIGNED_SHORT, GL_1_1.GL_SHORT : GL_1_1.GL_SHORT, GL_1_1.GL_UNSIGNED_INT : GL_1_1.GL_UNSIGNED_INT, GL_1_1.GL_INT : GL_1_1.GL_INT, GL_1_1.GL_FLOAT : GL_1_1.GL_FLOAT, GL_1_1.GL_DOUBLE : GL_1_1.GL_DOUBLE, GL_1_1.GL_BITMAP : GL_1_1.GL_UNSIGNED_BYTE, } ) images.TIGHT_PACK_FORMATS.update({ GL_1_2.GL_UNSIGNED_BYTE_3_3_2 : 3, GL_1_2.GL_UNSIGNED_BYTE_2_3_3_REV : 3, GL_1_2.GL_UNSIGNED_SHORT_4_4_4_4 : 4, GL_1_2.GL_UNSIGNED_SHORT_4_4_4_4_REV : 4, GL_1_2.GL_UNSIGNED_SHORT_5_5_5_1 : 4, GL_1_2.GL_UNSIGNED_SHORT_1_5_5_5_REV : 4, GL_1_2.GL_UNSIGNED_SHORT_5_6_5 : 3, GL_1_2.GL_UNSIGNED_SHORT_5_6_5_REV : 3, GL_1_2.GL_UNSIGNED_INT_8_8_8_8 : 4, GL_1_2.GL_UNSIGNED_INT_8_8_8_8_REV : 4, GL_1_2.GL_UNSIGNED_INT_10_10_10_2 : 4, GL_1_2.GL_UNSIGNED_INT_2_10_10_10_REV : 4, GL_1_1.GL_BITMAP: 8, # single bits, 8 of them... }) images.RANK_PACKINGS.update( { 4: [ # Note the sgis parameters are skipped here unless you import # the sgis texture4D extension... (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_ALIGNMENT, 1), ], 3: [ (GL_1_1.glPixelStorei,GL_1_2.GL_PACK_SKIP_IMAGES, 0), (GL_1_1.glPixelStorei,GL_1_2.GL_PACK_IMAGE_HEIGHT, 0), (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_ALIGNMENT, 1), ], 2: [ (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_ROW_LENGTH, 0), (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_SKIP_ROWS, 0), (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_ALIGNMENT, 1), ], 1: [ (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_SKIP_PIXELS, 0), (GL_1_1.glPixelStorei,GL_1_1.GL_PACK_ALIGNMENT, 1), ], } ) __all__ = ( 'glReadPixels', 'glReadPixelsb', 'glReadPixelsd', 'glReadPixelsf', 'glReadPixelsi', 'glReadPixelss', 'glReadPixelsub', 'glReadPixelsui', 'glReadPixelsus', 'glGetTexImage', 'glDrawPixels', 'glDrawPixelsb', 'glDrawPixelsf', 'glDrawPixelsi', 'glDrawPixelss', 'glDrawPixelsub', 'glDrawPixelsui', 'glDrawPixelsus', 'glTexSubImage2D', 'glTexSubImage1D', #'glTexSubImage3D', 'glTexImage1D', 'glTexImage2D', #'glTexImage3D', 'glGetTexImageb', 'glGetTexImaged', 'glGetTexImagef', 'glGetTexImagei', 'glGetTexImages', 'glGetTexImageub', 'glGetTexImageui', 'glGetTexImageus', 'glTexImage1Db', 'glTexImage2Db', #'glTexImage3Db', 'glTexSubImage1Db', 'glTexSubImage2Db', #'glTexSubImage3Db', 'glTexImage1Df', 'glTexImage2Df', #'glTexImage3Df', 'glTexSubImage1Df', 'glTexSubImage2Df', #'glTexSubImage3Df', 'glTexImage1Di', 'glTexImage2Di', #'glTexImage3Di', 'glTexSubImage1Di', 'glTexSubImage2Di', #'glTexSubImage3Di', 'glTexImage1Ds', 'glTexImage2Ds', #'glTexImage3Ds', 'glTexSubImage1Ds', 'glTexSubImage2Ds', #'glTexSubImage3Ds', 'glTexImage1Dub', 'glTexImage2Dub', #'glTexImage3Dub', 'glTexSubImage1Dub', 'glTexSubImage2Dub', #'glTexSubImage3Dub', 'glTexImage1Dui', 'glTexImage2Dui', #'glTexImage3Dui', 'glTexSubImage1Dui', 'glTexSubImage2Dui', #'glTexSubImage3Dui', 'glTexImage1Dus', 'glTexImage2Dus', #'glTexImage3Dus', 'glTexSubImage1Dus', 'glTexSubImage2Dus', #'glTexSubImage3Dus', #'glColorTable', #'glGetColorTable', #'glColorSubTable', #'glConvolutionFilter1D', #'glConvolutionFilter2D', #'glGetConvolutionFilter', #'glSeparableFilter2D', #'glGetSeparableFilter', #'glGetMinmax', ) def _get_texture_level_dims(target,level): """Retrieve texture dims for given level and target""" dims = [] dim = _types.GLuint() GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_1.GL_TEXTURE_WIDTH, dim ) dims = [dim.value] if target != GL_1_1.GL_TEXTURE_1D: GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_1.GL_TEXTURE_HEIGHT, dim ) dims.append( dim.value ) if target != GL_1_1.GL_TEXTURE_2D: GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_2.GL_TEXTURE_DEPTH, dim ) dims.append( dim.value ) return dims for suffix,type in [ ('b',GL_1_1.GL_BYTE), ('d',GL_1_1.GL_DOUBLE), ('f',GL_1_1.GL_FLOAT), ('i',GL_1_1.GL_INT), ('s',GL_1_1.GL_SHORT), ('ub',GL_1_1.GL_UNSIGNED_BYTE), ('ui',GL_1_1.GL_UNSIGNED_INT), ('us',GL_1_1.GL_UNSIGNED_SHORT), ]: def glReadPixels( x,y,width,height,format,type=type, array=None, outputType=bytes ): """Read specified pixels from the current display buffer This typed version returns data in your specified default array data-type format, or in the passed array, which will be converted to the array-type required by the format. """ x,y,width,height = asInt(x),asInt(y),asInt(width),asInt(height) arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ] if array is None: array = imageData = images.SetupPixelRead( format, (width,height), type ) owned = True else: if isinstance( array, integer_types): imageData = ctypes.c_void_p( array ) else: array = arrayType.asArray( array ) imageData = arrayType.voidDataPointer( array ) owned = False GL_1_1.glReadPixels( x,y, width, height, format,type, imageData ) if owned and outputType is bytes: return images.returnFormat( array, type ) else: return array globals()["glReadPixels%s"%(suffix,)] = glReadPixels def glGetTexImage( target, level,format,type=type, array=None, outputType=bytes ): """Get a texture-level as an image target -- enum constant for the texture engine to be read level -- the mip-map level to read format -- image format to read out the data type -- data-type into which to read the data array -- optional array/offset into which to store the value outputType -- default (bytes) provides string output of the results iff OpenGL.UNSIGNED_BYTE_IMAGES_AS_STRING is True and type == GL_UNSIGNED_BYTE. Any other value will cause output in the default array output format. returns the pixel data array in the format defined by the format, type and outputType """ arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ] if array is None: dims = _get_texture_level_dims(target,level) array = imageData = images.SetupPixelRead( format, tuple(dims), type ) owned = True else: if isinstance( array, integer_types): imageData = ctypes.c_void_p( array ) else: array = arrayType.asArray( array ) imageData = arrayType.voidDataPointer( array ) owned = False GL_1_1.glGetTexImage( target, level, format, type, imageData ) if owned and outputType is bytes: return images.returnFormat( array, type ) else: return array globals()["glGetTexImage%s"%(suffix,)] = glGetTexImage ## def glGetTexSubImage( target, level,format,type ): ## """Get a texture-level as an image""" ## dims = [GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_1.GL_TEXTURE_WIDTH )] ## if target != GL_1_1.GL_TEXTURE_1D: ## dims.append( GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_1.GL_TEXTURE_HEIGHT ) ) ## if target != GL_1_1.GL_TEXTURE_2D: ## dims.append( GL_1_1.glGetTexLevelParameteriv( target, level, GL_1_2.GL_TEXTURE_DEPTH ) ) ## array = images.SetupPixelRead( format, tuple(dims), type ) ## arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ] ## GL_1_1.glGetTexImage( ## target, level, format, type, ctypes.c_void_p( arrayType.dataPointer(array)) ## ) ## return array ## "%s = glGetTexImage"%(suffix) try: del suffix,type except NameError as err: pass # Now the real glReadPixels... def glReadPixels( x,y,width,height,format,type, array=None, outputType=bytes ): """Read specified pixels from the current display buffer x,y,width,height -- location and dimensions of the image to read from the buffer format -- pixel format for the resulting data type -- data-format for the resulting data array -- optional array/offset into which to store the value outputType -- default (bytes) provides string output of the results iff OpenGL.UNSIGNED_BYTE_IMAGES_AS_STRING is True and type == GL_UNSIGNED_BYTE. Any other value will cause output in the default array output format. returns the pixel data array in the format defined by the format, type and outputType """ x,y,width,height = asInt(x),asInt(y),asInt(width),asInt(height) arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ] if array is None: array = imageData = images.SetupPixelRead( format, (width,height), type ) owned = True else: if isinstance( array, integer_types): imageData = ctypes.c_void_p( array ) else: array = arrayType.asArray( array ) imageData = arrayType.voidDataPointer( array ) owned = False GL_1_1.glReadPixels( x,y,width,height, format,type, imageData ) if owned and outputType is bytes: return images.returnFormat( array, type ) else: return array def glGetTexImage( target, level,format,type, array=None, outputType=bytes ): """Get a texture-level as an image target -- enum constant for the texture engine to be read level -- the mip-map level to read format -- image format to read out the data type -- data-type into which to read the data array -- optional array/offset into which to store the value outputType -- default (bytes) provides string output of the results iff OpenGL.UNSIGNED_BYTE_IMAGES_AS_STRING is True and type == GL_UNSIGNED_BYTE. Any other value will cause output in the default array output format. returns the pixel data array in the format defined by the format, type and outputType """ arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ] if array is None: dims = _get_texture_level_dims(target,level) array = imageData = images.SetupPixelRead( format, tuple(dims), type ) else: if isinstance( array, integer_types): imageData = ctypes.c_void_p( array ) else: array = arrayType.asArray( array ) imageData = arrayType.voidDataPointer( array ) GL_1_1.glGetTexImage( target, level, format, type, imageData ) if outputType is bytes: return images.returnFormat( array, type ) else: return array INT_DIMENSION_NAMES = [ 'width','height','depth','x','y','z', 'xoffset','yoffset','zoffset', 'start', 'count', ] def asWrapper( value ): if not isinstance( value, wrapper.Wrapper ): return wrapper.wrapper( value ) return value def asIntConverter( value, *args ): if isinstance( value, float ): return int(round(value,0)) return value def setDimensionsAsInts( baseOperation ): """Set arguments with names in INT_DIMENSION_NAMES to asInt processing""" baseOperation = asWrapper( baseOperation ) argNames = getattr( baseOperation, 'pyConverterNames', baseOperation.argNames ) for i,argName in enumerate(argNames): if argName in INT_DIMENSION_NAMES: baseOperation.setPyConverter( argName, asIntConverter ) return baseOperation class ImageInputConverter( object ): def __init__( self, rank, pixelsName=None, typeName='type' ): self.rank = rank self.typeName = typeName self.pixelsName = pixelsName def finalise( self, wrapper ): """Get our pixel index from the wrapper""" self.typeIndex = wrapper.pyArgIndex( self.typeName ) self.pixelsIndex = wrapper.pyArgIndex( self.pixelsName ) def __call__( self, arg, baseOperation, pyArgs ): """pyConverter for the pixels argument""" images.setupDefaultTransferMode() images.rankPacking( self.rank ) type = pyArgs[ self.typeIndex ] arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE[ type ] ] return arrayType.asArray( arg ) # def cResolver( self, array ): # return array # return ctypes.c_void_p( arrays.ArrayDatatype.dataPointer( array ) ) class TypedImageInputConverter( ImageInputConverter ): def __init__( self, rank, pixelsName, arrayType, typeName=None ): self.rank = rank self.arrayType = arrayType self.pixelsName = pixelsName self.typeName = typeName def __call__( self, arg, baseOperation, pyArgs ): """The pyConverter for the pixels""" images.setupDefaultTransferMode() images.rankPacking( self.rank ) return self.arrayType.asArray( arg ) def finalise( self, wrapper ): """Get our pixel index from the wrapper""" self.pixelsIndex = wrapper.pyArgIndex( self.pixelsName ) def width( self, pyArgs, index, wrappedOperation ): """Extract the width from the pixels argument""" return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[0] def height( self, pyArgs, index, wrappedOperation ): """Extract the height from the pixels argument""" return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[1] def depth( self, pyArgs, index, wrappedOperation ): """Extract the depth from the pixels argument""" return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[2] def type( self, pyArgs, index, wrappedOperation ): """Provide the item-type argument from our stored value This is used for pre-bound processing where we want to provide the type by implication... """ return self.typeName class CompressedImageConverter( object ): def finalise( self, wrapper ): """Get our pixel index from the wrapper""" self.dataIndex = wrapper.pyArgIndex( 'data' ) def __call__( self, pyArgs, index, wrappedOperation ): """Create a data-size measurement for our image""" arg = pyArgs[ self.dataIndex ] return arraydatatype.ArrayDatatype.arrayByteCount(arg) DIMENSION_NAMES = ( 'width','height','depth' ) PIXEL_NAMES = ( 'pixels', 'row', 'column', ) DATA_SIZE_NAMES = ( 'imageSize', ) def setImageInput( baseOperation, arrayType=None, dimNames=DIMENSION_NAMES, pixelName="pixels", typeName=None ): """Determine how to convert "pixels" into an image-compatible argument""" baseOperation = asWrapper( baseOperation ) # rank is the count of width,height,depth arguments... rank = len([ # rank is the number of dims we want, not the number we give... argName for argName in baseOperation.argNames if argName in dimNames ]) + 1 if arrayType: converter = TypedImageInputConverter( rank, pixelName, arrayType, typeName=typeName ) for i,argName in enumerate(baseOperation.argNames): if argName in dimNames: baseOperation.setPyConverter( argName ) baseOperation.setCConverter( argName, getattr(converter,argName) ) elif argName == 'type' and typeName is not None: baseOperation.setPyConverter( argName ) baseOperation.setCConverter( argName, converter.type ) else: converter = ImageInputConverter( rank, pixelsName=pixelName, typeName=typeName or 'type' ) for argName in baseOperation.argNames: if argName in DATA_SIZE_NAMES: baseOperation.setPyConverter( argName ) baseOperation.setCConverter( argName, converter.imageDataSize ) baseOperation.setPyConverter( pixelName, converter, ) # baseOperation.setCResolver( # pixelName, converter.cResolver # ) return baseOperation glDrawPixels = setDimensionsAsInts( setImageInput( GL_1_1.glDrawPixels ) ) glTexSubImage2D = setDimensionsAsInts( setImageInput( GL_1_1.glTexSubImage2D ) ) glTexSubImage1D = setDimensionsAsInts( setImageInput( GL_1_1.glTexSubImage1D ) ) glTexImage2D = setDimensionsAsInts( setImageInput( GL_1_1.glTexImage2D ) ) glTexImage1D = setDimensionsAsInts( setImageInput( GL_1_1.glTexImage1D ) ) def typedImageFunction( suffix, arrayConstant, baseFunction ): """Produce a typed version of the given image function""" functionName = baseFunction.__name__ functionName = '%(functionName)s%(suffix)s'%locals() arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ arrayConstant ] function = setDimensionsAsInts( setImageInput( baseFunction, arrayType, typeName = arrayConstant, ) ) return functionName, function def _setDataSize( baseFunction, argument='imageSize' ): """Set the data-size value to come from the data field""" converter = CompressedImageConverter() return asWrapper( baseFunction ).setPyConverter( argument ).setCConverter( argument, converter ) def compressedImageFunction( baseFunction ): """Set the imageSize and dimensions-as-ints converters for baseFunction""" return setDimensionsAsInts( _setDataSize( baseFunction, argument='imageSize' ) ) for suffix,arrayConstant in [ ('b', GL_1_1.GL_BYTE), ('f', GL_1_1.GL_FLOAT), ('i', GL_1_1.GL_INT), ('s', GL_1_1.GL_SHORT), ('ub', GL_1_1.GL_UNSIGNED_BYTE), ('ui', GL_1_1.GL_UNSIGNED_INT), ('us', GL_1_1.GL_UNSIGNED_SHORT), ]: for functionName in ( 'glTexImage1D','glTexImage2D', 'glTexSubImage1D','glTexSubImage2D', 'glDrawPixels', #'glTexSubImage3D','glTexImage3D', # extension/1.2 standard ): functionName, function = typedImageFunction( suffix, arrayConstant, getattr(GL_1_1,functionName), ) globals()[functionName] = function try: del function, functionName except NameError as err: pass try: del suffix,arrayConstant except NameError as err: pass