How would you set the color of OpenGl display to green and change the fill color to have texture? Do a demonstration example with the circle with parameter starting coordinates (-3,1) and radius of 4 cm and texture type of the circle of your choice ( mention in the write-up what you used)
- Texture is basically an image applied to a polygon or a mesh
- 1D : Typically used for storing data or color gradients,
- 2D : This is what we used for our circle. It's the most common texture type, representing images.2D(what we used)
- 3D : Used for volumetric textures like smoke or clouds.
- Square texture are often preferred due to their aspect ratio and simplicity in mapping coordinates.
- Two main types are used to determine how texture pixels (texels) are mapped onto the shape:
- GL_NEAREST: Provides a pixelated look, as it uses the nearest texel's color.
- GL_LINEAR: Smooths out the texture by averaging the colors of nearby texels.
- Different modes control how textures are applied:
- GL_REPEAT: Repeats the texture outside its normal 0-1 coordinate range.
- GL_MIRRORED_REPEAT: Like repeat, but mirrors the texture with each repeat.
- GL_CLAMP_TO_EDGE: Stretches the edge pixels of the texture to fill the shape.
- GL_CLAMP_TO_BORDER: Fills beyond the edge with a specified border color.
- Mipmaps are used for efficiently rendering textures at various distances by using scaled-down versions of the texture.
- Coordinates determine how the texture is applied to the shape. They are typically defined in a normalized space (0 to 1).
The Material class in material.py is the one responsible for initializing the texture
that we are using. It takes in a single parameter named filepath
that is the path to the texture to be used.
The __init__ method begins by generating a single texture using glGenTextures(1) and
the texture is made the current texture using glBindTexture thus priming it for further
manipulation using the glTexParameteri function calls.
The first pair of glTexParameteri calls set the texture mapping mode to GL_REPEAT
Then we load our image using pygame.image.load
and pass it in to the glTexImage2D function which associates our texture with the loaded image.
glTexImage2D has the following prototype:
glTexImage2D(target, level, internalFormat, width, height, border, externalFormat, type, *pointer)
- target - The currently bound texture,
- level - The mipmap level to associate the current level image with. The image with the highest resolution
- (the original image) is at level 0.
- internalFormat - The format with which OpenGL will interpret the image
- width and height - The dimensions of the image
- border - The width of the border around the image. Set to 0 for no border
- externalFormat - The format with which the external image is represented with
- type - The underlying data type of the image data
- *pointer - A pointer to the image data (in Python, we can just pass the reference of the variable)
We then conclude the __init__ method by calling glGenerateMipMap and glEnable which gives us the ability
to draw the textures using OpenGL immediate mode (without shaders and VBOs and VAOs)
The destroy method call frees the memory allocated during the creation of the texture.
This defines the main application logic. It uses Pygame to initialize the drawing window instead of other libraries traditionally used alongside OpenGL like GLUT (or FreeGLUT) and GLFW.
Henceforth, pygame is referred to by the alias pg
The window is created by calling the method pg.display.setmode which receives
the size of the window and the flag pg.OPENGL | pg.DOUBLEBUF | pg.RESISABLE for a resizable window
with an OpenGL drawing context and double-buffering.
The method pg.display.gl_set_attributes here is used to set the GL_CONTEXT_PROFILE_MASK to
GL_CONTEXT_PROFILE_COMPATIBILITY so that we can use OpenGL immediate mode features.
(Here, we are not using modern shader-based OpenGL)
The resize method defines the size and projection for our viewport and is called
not only when the window is created, but also when the window is resized (check the event loop in main_loop)
The dimensions of our viewing box is as defined in the call to glOrtho.
The field circle_attributes is a 4-tuple of the form
(x, y, z, r) that stores the x, y and z coordinates of the center of the circle and the radius of the circle.
Currently, it is hard-coded to 4 as required by our question. This is what is passed to the generate_circle_points
explained below.
The points for the circle are generated by the creatively named generate_circle_points method based
on the mathematical definition of a circle:
where
The s and t coordinates (discussed earlier) of the texture are also generated at the same time, starting from the in this method using the formulas:
generate_circle_points thus returns a list of 5-tuples of the form
(x, y, z, s, t) with the first three being the points of the vertex of the circle
and the s, t coordinates of the texture to be painted on the circle.
These are used by the draw_circle method where the glTexCoord2f and glVertex3f
methods are called. The color is filled with a green colour in the call to glColor3f for
all the vertices as required by the question. This gives a green tint to the image.
Here is the green circle, without a texture:
Here is the output with a 1200 by 1600 JPEG image:
Compare this with the original image:
We can see that the image is slightly distorted. This is a consequence of what we mention earlier in the texture size section. OpenGL is better optimized for square images whose sizes are powers of 2. The image here does not fit these criteria.
Compare this with the output of a 512 by 512 image BMP image (pg.image.convert handles the different file formats for
us)
and the original image
The width of the image is much better respected in the latter
The destroy method of the CompatibilityApp class frees all the resources
it has created by calling the destroy method of the instance of the Material class it creates
(in the code, it is called self.edwin_texture), and pg.quit for
Pygame-specific resources like the window. This method is called whenever there is a pg.QUIT event is received in the
event loop.
This is the entry point of the application, and it just creates an instance of CompatibilityApp and calls the
instance's main_loop method.
These are links to YouTube videos that were immensely helpful while doing exploring textures what textures are and how to use them in OpenGL and using Python.
-
OpenGL with Python 3: Adding Textures - This showed the setup required to add a texture in Python. However, the video was done using shader-based OpenGL
-
OpenGL Tutorial 6 - Textures - Actually the screenshots in the first part of this document are sourced from this video. It explains what textures are really well. However, this is also done in shader-based OpenGL.
-
OpenGL with Python: Intro and Setup shows how to configure one's environment to work with OpenGL and Pygame.
The book Computer Graphics Through OpenGL - From Theory to Experiments (3rd Edition) by Sumanta Guha was a key reference
Pillow does most of the heavy-lifting here. Tkinter is just used to provide a window and a canvas to draw the image.
The code is summarized by the points below:
- Create a window
- Create an Image object using the original image
- Create a mask (a circular mask in our case)
- Tint the original image (in our case using the colour green)
- Crop the tinted image
- Convert the image to a form that Tkinter can display
- Create a Canvas and pack it into the window
- Draw the converted image into the canvas
Now we take a close look at a few key functions
Creates a new image in and draws a circle over it using the ImageDraw.ellipse method
The drawn-over image is returned as our mask
The overlay is created by blending a new image using the original image converted into black and white and the colour desired (by default, green)
The black and white version of the image is obtained using the
ImageEnhance.Colour call with the enhance factor set to 0.
This method fits the image into the mask using ImageOps.fit. The centering
is set to (0.5, 0.5)
The line cropped.putalpha(img_mask) adds the alpha channel to the image.
The following StackOverflow questions and answers were used for most of the code:
Create circular image PIL Tkinter - https://stackoverflow.com/questions/30602460/create-circular-image-pil-tkinter
Colorize image while preserving transparency - https://stackoverflow.com/questions/12251896/colorize-image-while-preserving-transparency-with-pil
The Pillow documentation was a big help in making sense of the contents of the above pages.
The code also includes an implementation using OpenCV which is follows this YouTube video.











