How to use glReadPixels in Python and OpenGL? - python

How can I get the value of pixel color using glReadPixels()? I did so many try but getting wrong value.
My background color is blue(0,1,1) and I have drawn a circle with boundary color red(1,0,0) and I want to get the color of any of boundary point. So it must give me red. but I am getting background color.
Here is my code in Python3 and OpenGL
from OpenGL.GLU import *
from OpenGL.GLUT import *
import time
from math import *
import numpy
import sys
def init():
glClearColor(0.0,1.0,1.0,0.0)
glClear(GL_COLOR_BUFFER_BIT)
glPointSize(3.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(0.0,640.0,0.0,640.0)
def circle():
for i in range(361):
m=float(50*cos(i*pi/180.0))+320
n=float(50*sin(i*pi/180.0))+320
setpixc(m,n)
print(m,n)
redinput()
def redinput():
global x,y
x=int(input("enter x:"))
y=int(input("enter y:"))
setpixc(x,y)
pixel=[]
c=glReadPixels(x,y,1.0,1.0,GL_RGB,GL_UNSIGNED_BYTE,None)
print(c)
string_pixels=numpy_pixel.tolist()
print(string_pixels)
def setpixc(xcor,ycor):
glBegin(GL_POINTS)
glColor3f(1.0,0.0,0.0)
glVertex2f(xcor,ycor)
glEnd()
glFlush()
def Display():
circle()
print("hello")
def main():
glutInit(sys.argv)
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
glutInitWindowSize(600,600)
glutInitWindowPosition(10,10)
glutCreateWindow("line-dda")
glutDisplayFunc(Display)
init()
glutMainLoop()
main()

You are using an orthographic projection, which which projects the coordinates to the rectangle form (0, 0) to (640, 640):
gluOrtho2D(0.0,640.0,0.0,640.0)
But your window size is (600, 600):
glutInitWindowSize(600,600)
This causes that the coordinates in the range from (0, 0) to (640, 640) are drawn to the viewport from (0, 0) to (600, 600), by glVertex2f:
But when the coordinates are read by glReadPixels, then you would have to use viewport (pixel) coordinates.
To solve your is you can change the window size from (600, 600) to (640, 640):
glutInitWindowSize(640, 640)
Now e.g.
x=270
y=320
will return a red pixel.
Note, if you don't want to change the window size, then you would have to scale the input coordinates by 600/640.
scale = 600/640
c=glReadPixels(x*scale,y*scale,1.0,1.0,GL_RGB,GL_UNSIGNED_BYTE,None)
e.g.
x = 270 * 600 / 640 = 253
y = 320 * 600 / 640 = 300
Further note, that drawing by glBegin/glEnd sequences is deprecated since several years.
Read about Fixed Function Pipeline and see Vertex Specification and Shader for a state of the art way of rendering.
Anyway, I recommend to use double buffering
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
and to do a single buffer swap, after the entire circle was drawn. Skip the glFlush call in setpixc and add a single glutSwapBuffers call to the Display function and don't forget to clear the display before rendering:
def Display():
glClear(GL_COLOR_BUFFER_BIT)
circle()
glutSwapBuffers()
glutPostRedisplay()
redinput()
print("hello")
It is up to you if you want to draw the circle by single points
def circle():
glPointSize(3.0)
glColor3f(1.0,0.0,0.0)
glBegin(GL_POINTS)
for i in range(360):
m=float(50*cos(i*pi/180.0))+320
n=float(50*sin(i*pi/180.0))+320
glVertex2f(m,n)
glEnd()
or a coherent line:
def circle():
glLineWidth(3.0)
glColor3f(1.0,0.0,0.0)
glBegin(GL_LINE_LOOP)
for i in range(360):
m=float(50*cos(i*pi/180.0))+320
n=float(50*sin(i*pi/180.0))+320
glVertex2f(m,n)
glEnd()
If you want to get the color of a pixel by a mouse click, the you can set a mouse call back by glutMouseFunc:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from math import *
def init():
global width, height
glClearColor(0.0, 1.0, 1.0, 0.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluOrtho2D(0.0, width, 0.0, height)
def circle():
glLineWidth(3.0)
glColor3f(1.0, 0.0, 0.0)
glBegin(GL_LINE_LOOP)
for i in range(360):
m=float(50*cos(i*pi/180.0))+320
n=float(50*sin(i*pi/180.0))+320
glVertex2f(m, n)
glEnd()
def Mouse(button, state, x, y):
global mouse_x, mouse_y, get_input
if button == GLUT_LEFT_BUTTON and state == GLUT_DOWN:
mouse_x = x
mouse_y = height - y # the y coordinate of the mouse has to be flipped
get_input = True
def redinput(x, y):
c = glReadPixels(x, y, 1.0, 1.0, GL_RGB,GL_UNSIGNED_BYTE, None)
print(c)
def Display():
global mouse_x, mouse_y, get_input
glClear(GL_COLOR_BUFFER_BIT)
circle()
glutSwapBuffers()
glutPostRedisplay()
if get_input:
redinput(mouse_x, mouse_y)
get_input=False
def main():
global width, height
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(width, height)
glutInitWindowPosition(10, 10)
glutCreateWindow("line-dda")
glutDisplayFunc(Display)
glutMouseFunc(Mouse)
init()
glutMainLoop()
width = 640
height = 640
mouse_x = 0
mouse_y = 0
get_input = False
main()

There are couple of problems with your code.
your window size is different from data you are using in gluOrtho2D
you are relying on pixel exact rasterization, and OpenGL does not guarantee it.
The answer you are searching for is described in "Red Book" (i.e. OpenGL Programming Guide), specifically in Appendix G, Programming Tips. I'd also suggest you to read Appendix H, Invariance. Online version can be found on following link: https://www.glprogramming.com/red/
Also,
you do not need to call glFlush after every single point drawn, call it once just before glReadPixels...
you are using glBegin/glEnd pair for every single point, which is huge waste of
resources. you can draw complete circle using one glBegin/glEnd pair:
glBegin(GL_POINTS)
glColor3f(1.0, 0.0, 0.0)
for i in range(361):
x=float(50*cos(i*pi/180.0))+320
y=float(50*sin(i*pi/180.0))+320
glVertex2f(x,y)
glEnd()
you are using very dense set of GL_POINTS to draw circle, but this will not prodice correct circle. If radius is smaller, you will have multiple rasterization of same window pixel. If you increase radius enough, it will result in set of unconnected points. In your situation, I would use GL_LINE_LOOP:
glBegin(GL_LINE_LOOP)
glColor3f(1.0, 0.0, 0.0)
for i in range(0, 360, 5):
x=float(50*cos(i*pi/180.0))+320
y=float(50*sin(i*pi/180.0))+320
glVertex2f(x,y)
glEnd()
last but not least, this is ancient way of OpenGL usage. Unless you have very good reason, I'd suggest to move to some newer OpenGL version.

Related

Pygame and PyOpenGL: No shapes appear on screen

I have created a complete snake game using C++ and OpenGL before, and I want to do the same using Python, pygame, and PyOpenGL. The current problem I have is that after I spawn a fruit, it does not appear on the screen. Here's the code for my main function:
def main(): # Main function
# Initialize game components
game = Game(800, 600)
test_fruit = game.spawn_fruit(Point(100, 100))
# Initialize pygame module
pygame.init()
pygame.display.set_mode(game.get_window_size(), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Python Game")
# Define variable to control main loop
running = True
# Main loop
while running:
# event handling, gets all event from the event queue
for event in pygame.event.get():
# only do something if the event is of type QUIT
if event.type == pygame.QUIT:
# change the value to False, to exit the main loop
running = False
# Modify game properties
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
game.draw_shapes()
pygame.display.flip()
pygame.time.wait(5)
It's possible I'm missing a pygame or pyopengl function, but I'm not sure. I've also tried changing pygame.display.flip() to pygame.display.update(), yet it gives me an error ("cannot update an OpenGL display") instead.
Here's the code for the shape I am attempting to display:
class Circle:
def __init__(self, pivot: Point, radius: int, sides: int, fill: bool, color: Color):
self.pivot = pivot
self.radius = radius
self.sides = sides
self.fill = fill
self.color = color
# Draw the shape of the circle
def draw(self):
glColor3f(self.color.r, self.color.g, self.color.b)
if self.fill:
glBegin(GL_POLYGON)
else:
glBegin(GL_LINE_LOOP)
for i in range(100):
cosine = self.radius * cos(i*2*pi/self.sides) + self.pivot.x
sine = self.radius * sin(i*2*pi/self.sides) + self.pivot.y
glVertex2f(cosine, sine)
glEnd()
OpenGL coordinates are in range [-1.0, 1.0] (Normalized Device Space). The Normalized device space is a unique cube from the left, bottom, near (-1, -1, -1) to the right, top, far (1, 1, 1).
If you want to use "window" coordinates, you must specify an Orthographic projection using glOrtho:
glOrtho(0, 800, 600, 0, -1, 1)
Choose the matrix mode with glMatrixMode and load the Identity matrix with glLoadIdentity.
Example:
def main(): # Main function
# Initialize game components
game = Game(800, 600)
test_fruit = game.spawn_fruit(Point(100, 100))
# Initialize pygame module
pygame.init()
pygame.display.set_mode(game.get_window_size(), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Python Game")
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, 800, 600, 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# Define variable to control main loop
running = True
# [...]

Why this octagon doesn't fit window?

I was trying to create an octagon:
import pyglet
from pyglet.gl import *
class mywindow(pyglet.window.Window):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.set_minimum_size(300,300)
def on_draw(self):
self.clear()
glBegin(GL_POLYGON)
glColor3ub(255,0,0)
glVertex2f(0,0)
glVertex2f(1.0,0)
glVertex2f(1.5,0.5)
glVertex2f(1.5,1.5)
glVertex2f(1.0,2.0)
glVertex2f(0,2.0)
glVertex2f(-0.5,1.5)
glVertex2f(-0.5,0.5)
glEnd()
def on_resize(self, width, height):
glViewport(10,10, width, height)
window = mywindow(300,300,"deneme",True)
pyglet.app.run()
everything seems fine. But when I run this code I see this output:
any idea how can I fix it?
Your polygon is simply too big !
By dividing the coordinates by 10 :
glVertex2f(0,0)
glVertex2f(0.1,0)
glVertex2f(0.15,0.05)
glVertex2f(0.15,.15)
glVertex2f(0.1,.2)
glVertex2f(0,0.2)
glVertex2f(-0.05,0.15)
glVertex2f(-0.05,0.05)
You will be able to see your octagon now
Most of the octagon is out of the viewport. By default the bottom left coordinate of the viewport is (-1, -1) and the top right is (1, 1).
You can set an orthographic projection matrix, to change the projected area (respectively volume), by glOrtho:
class mywindow(pyglet.window.Window):
# [...]
def on_resize(self, width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-3, 3, -3, 3, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

pyglet drawing primitive GL_POINT. A buffer issue?

Beginner in pyglet. I have an issue when drawing GL_POINT using pyglet.graphicss.draw(). I want this GL_POINT to be drawn after another on the next pixel buffer, but it seems the function does not keep the last GL_POINT to be drawn on the next pixel buffer.
import pyglet
from pyglet.gl import *
from pyglet.window import key # for key input, on_key_press
window = pyglet.window.Window(800, 600) # create a window object with the resolution of 800x600
window.set_caption('window title')
glClear(GL_COLOR_BUFFER_BIT)
#window.event
def on_key_press(symbol, modifiers): # keyboard input handler
if symbol == key.L: # Drawing a center point
print("DRAWING TEST A POINT (400, 300)")
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
elif symbol == key.K: # Drawing a bit further 100 more horizontally from center point
print("DRAWING TEST A POINT (500, 300)")
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (500, 300))
)
pyglet.app.run()
Pressing L would draw a center point.
Then pressing K would draw 100 more horizontally from the center point with the last center point gone.
Where is the bug? is there something wrong with my code? if not,
my guess would be, does pyglet.graphicss.draw() function actually redraw one after another primitive shape? How do I code to draw one after another?
The issue is caused by Double buffering. You can solve the issue by drawing the point to both buffers. Draw the point twice and swap the OpenGL front and back buffers in between by (flip).
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
window.flip()
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
But I recommend to add the points to a list and to draw the list. e.g.:
import pyglet
from pyglet.gl import *
from pyglet.window import key # for key input, on_key_press
points = []
window = pyglet.window.Window(800, 600) # create a window object with the resolution of 800x600
window.set_caption('window title')
glClear(GL_COLOR_BUFFER_BIT)
#window.event
def on_key_press(symbol, modifiers): # keyboard input handler
global points
if symbol == key.L: # Drawing a center point
print("DRAWING TEST A POINT (400, 300)")
points += [400, 300]
elif symbol == key.K: # Drawing a bit further 100 more horizontally from center point
print("DRAWING TEST A POINT (500, 300)")
points += [500, 300]
pyglet.graphics.draw(len(points) // 2, pyglet.gl.GL_POINTS, ('v2i', points))
pyglet.app.run()

How do I make 3D in pyglet?

I was trying to create using OpenGL, Python and pyglet, a flat triangle in 3D space, I saw some tutorials on the internet, some videos on YouTube, and in the end I wrote this code down there, the problem is that it did not work as I expected, I thought that if I tried to spin, I would see the triangle turning flat, and when I walked away, the triangle did not have to diminish?
import pyglet
from pyglet.gl import *
config = Config(sample_buffers=1, samples=8)
tela = pyglet.window.Window(height=500, width=500, config=config)
glViewport(0,0,500,500)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(35,1,0.1,1000)
glMatrixMode(GL_MODELVIEW)
#tela.event
def on_draw():
glBegin(GL_POLYGON)
glVertex3f(10,10,0)
glVertex3f(100,10,0)
glVertex3f(50,100,0)
glEnd()
glFlush()
#tela.event
def on_key_press(s,m):
tela.clear()
if s == pyglet.window.key.W:
glTranslatef(0,0,1)
if s == pyglet.window.key.S:
glTranslatef(0,0,-1)
if s == pyglet.window.key.A:
glRotatef(1,0,1,0)
if s == pyglet.window.key.D:
glRotatef(-1,0,1,0)
pyglet.app.run()
When I run the code this appears:
And when I try to spin the scenario it happens:
Does anyone know where I'm going wrong?
The initialization of the viewport and the sting pf the projection and model view matrix is useless
glViewport(0,0,500,500)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(35,1,0.1,1000)
glMatrixMode(GL_MODELVIEW)
because the viewport and an orthographic projection is set when the application is started.
See pyglet - The OpenGL interface:
[...] pyglet sets up the viewport and an orthographic projection on each window automatically.
If you would use the perspective projection
gluPerspective(35,1,0.1,1000)
then the triangle would disappear, because it would be clipped by the near plane of the perspective projection (0.1).
To solve the issue, put the setup of perspective projection to the draw event:
#tela.event
def on_draw():
tela.clear()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(90, 1, 0.1, 100)
I thought that if I tried to spin, I would see the triangle turning flat, and when I walked away, the triangle did not have to diminish?
In view space, the x axis points from the left to the right and the y axis points from the bottom to the top.
To rotate in the XY plane, you have to rotate around the Z axis.
Define a position and an Y-angle for the triangle. The Z coordinate has to be negative and the distance to the object has to be in between the near and far plane. If near is 0.1 and far is 100, then:
0.1 <= -z <= 100
e.g.
pos = [0, 0, -20]
rot_y = 0
Manipulate the position and the angle in the event:
#tela.event
def on_key_press(s,m):
global pos_z, rot_y
if s == pyglet.window.key.W:
pos[2] -= 1
if s == pyglet.window.key.S:
pos[2] += 1
if s == pyglet.window.key.A:
rot_y += 5
if s == pyglet.window.key.D:
rot_y -= 5
Apply the translation and the rotation to the model view matrix stack in draw:
#tela.event
def on_draw():
global pos_z, rot_y
# [...]
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslatef(*pos)
glRotatef(rot_y, 0, 1, 0)
Draw an object which is arranged around (0, 0, 0). Note the position of the object is set by pos and in perspective projection the origin (0, 0, 0) is in the center of the window:
glBegin(GL_POLYGON)
glVertex3f(-5,-5,0)
glVertex3f(5,-5,0)
glVertex3f(0,5,0)
glEnd()
Full code with the suggested changes applied:
import pyglet
from pyglet.gl import *
pos = [0, 0, -20]
rot_y = 0
config = Config(sample_buffers=1, samples=8)
tela = pyglet.window.Window(height=500, width=500, config=config)
#tela.event
def on_draw():
global pos_z, rot_y
tela.clear()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(90, 1, 0.1, 100)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslatef(*pos)
glRotatef(rot_y, 0, 1, 0)
glBegin(GL_POLYGON)
glVertex3f(-5,-5,0)
glVertex3f(5,-5,0)
glVertex3f(0,5,0)
glEnd()
glFlush()
#tela.event
def on_key_press(s,m):
global pos_z, rot_y
if s == pyglet.window.key.W:
pos[2] -= 1
if s == pyglet.window.key.S:
pos[2] += 1
if s == pyglet.window.key.A:
rot_y += 5
if s == pyglet.window.key.D:
rot_y -= 5
pyglet.app.run()

Detecting if an arc has been clicked in pygame

I am currently trying to digitalize an boardgame I invented (repo: https://github.com/zutn/King_of_the_Hill). To make it work I need to check if one of the tiles (the arcs) on this board have been clicked. So far I have not been able to figure a way without giving up the pygame.arc function for drawing. If I use the x,y position of the position clicked, I can't figure a way out to determine the exact outline of the arc to compare to. I thought about using a color check, but this would only tell me if any of the tiles have been clicked. So is there a convenient way to test if an arc has been clicked in pygame or do I have to use sprites or something completely different? Additionally in a later step units will be included, that are located on the tiles. This would make the solution with the angle calculation postet below much more diffcult.
This is a simple arc class that will detect if a point is contained in the arc, but it will only work with circular arcs.
import pygame
from pygame.locals import *
import sys
from math import atan2, pi
class CircularArc:
def __init__(self, color, center, radius, start_angle, stop_angle, width=1):
self.color = color
self.x = center[0] # center x position
self.y = center[1] # center y position
self.rect = [self.x - radius, self.y - radius, radius*2, radius*2]
self.radius = radius
self.start_angle = start_angle
self.stop_angle = stop_angle
self.width = width
def draw(self, canvas):
pygame.draw.arc(canvas, self.color, self.rect, self.start_angle, self.stop_angle, self.width)
def contains(self, x, y):
dx = x - self.x # x distance
dy = y - self.y # y distance
greater_than_outside_radius = dx*dx + dy*dy >= self.radius*self.radius
less_than_inside_radius = dx*dx + dy*dy <= (self.radius- self.width)*(self.radius- self.width)
# Quickly check if the distance is within the right range
if greater_than_outside_radius or less_than_inside_radius:
return False
rads = atan2(-dy, dx) # Grab the angle
# convert the angle to match up with pygame format. Negative angles don't work with pygame.draw.arc
if rads < 0:
rads = 2 * pi + rads
# Check if the angle is within the arc start and stop angles
return self.start_angle <= rads <= self.stop_angle
Here's some example usage of the class. Using it requires a center point and radius instead of a rectangle for creating the arc.
pygame.init()
black = ( 0, 0, 0)
width = 800
height = 800
screen = pygame.display.set_mode((width, height))
distance = 100
tile_num = 4
ring_width = 20
arc = CircularArc((255, 255, 255), [width/2, height/2], 100, tile_num*(2*pi/7), (tile_num*(2*pi/7))+2*pi/7, int(ring_width*0.5))
while True:
fill_color = black
for event in pygame.event.get():
# quit if the quit button was pressed
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
x, y = pygame.mouse.get_pos()
# Change color when the mouse touches
if arc.contains(x, y):
fill_color = (200, 0, 0)
screen.fill(fill_color)
arc.draw(screen)
# screen.blit(debug, (0, 0))
pygame.display.update()

Categories

Resources