OpenGL drawn area occupies only lower left quadrant of the available window - python

I only just started with OpenGL and PyOpenGL and am using tutorial code from this page https://noobtuts.com/python/opengl-introduction. However I quickly ran into the following problem: while the code successfully draws what is intended the drawing cannot occupy more than the lower left quadrant of my window. E.g. in the below I am setting the size and position of the rectangle such that it occupies the full window, as you can see in the code below I set the rectangle’s width and height to the window’s width and height and the position is 0,0 so I would expect the full window to become blue but this is not happening as you can see below. I am on Mac OS Catalina and running PyOpenGL on Python 3.
I have seen elsewhere that there is something to do with Catalina at this place: https://github.com/redeclipse/base/issues/920
and this place https://github.com/ioquake/ioq3/issues/422#issuecomment-541193050
However this is way too advanced for me to understand.
Would anyone know how that can be solved perhaps?
Thanks ahead for your help
from OpenGL import *
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
window = 0 # glut window number
width, height = 500, 400 # window size
def refresh2d(width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0.0, width, 0.0, height, 0.0, 1.0)
glMatrixMode (GL_MODELVIEW)
glLoadIdentity()
def draw_rect(x, y, width, height):
glBegin(GL_QUADS) # start drawing a rectangle
glVertex2f(x, y) # bottom left point
glVertex2f(x + width, y) # bottom right point
glVertex2f(x + width, y + height) # top right point
glVertex2f(x, y + height) # top left point
glEnd() # done drawing a rectangle
def draw(): # ondraw is called all the time
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # clear the screen
glLoadIdentity() # reset position
refresh2d(width, height) # set mode to 2d
glColor3f(0.0, 0.0, 1.0) # set color to blue
draw_rect(0, 0, 500, 400) # rect at (0, 0) with width 500, height 400
glutSwapBuffers() # important for double buffering
# initialization
glutInit() # initialize glut
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowSize(width, height) # set window size
glutInitWindowPosition(0, 0) # set window position
window = glutCreateWindow("my first attempt") # create window with title
glutDisplayFunc(draw) # set draw function callback
glutIdleFunc(draw) # draw all the time
glutMainLoop() # start everything
However this isn't working. I am definitely getting a window where the blue rectangle occupies only the lower left quadrant.

FWIW, using glfw I'm able to get around this problem:
width = 1280
height = 1024
win = glfw.CreateWindow(width, height, "window title")
fb_width, fb_height = glfw.GetFramebufferSize(win)
glViewport(0, 0, fb_width, fb_height) # <--- this is the key line

you can install modified glut http://iihm.imag.fr/blanch/software/glut-macosx/
or you can do
glViewport(0, 0, width*2, height*2)
if you don't care about DPI

Related

Is there a way to display a pygame window over OpenGL?

I've been meddling around with PyOpenGL and pygame, and I managed to create an FPS-style camera object. Now I want to add a crosshairs in the middle of the screen, and potentially expand to display statistics on the sides of the window.
I've already looked into this, and it seems like you have to do some weird stuff with OpenGL like disabling depth test and changing the projection matrix, and until now none of that actually renders anything, and reduces performance.
It seems to me that it should be very easy, as all I want is something that is over everything else, and doesn't ever move. Is there really no way to tell pygame to draw over OpenGL so I can just draw two lines in the middle of the screen?
No there is no specified way to do that. Do it in OpenGL it is not that complicate.
According to your previous questions, I assume you want to do it in immediate mode using glBegin - glEnd sequences.
In the following I assume that width is the width of the window and height its height. You have to disable the depth test and back up the current matrices by glPushMatrix/glPopMatrix. Load the Identity matrix for the model view matrix and setup an orthographic projection corresponding to the window size (glOrtho):
cross_size = 100
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(0, width, height, 0, -1, 1)
glDisable(GL_DEPTH_TEST)
glColor3ub(128, 128, 128) # color of the crosshair
glBegin(GL_LINES)
glVertex2f(width/2 - cross_size/2, height/2)
glVertex2f(width/2 + cross_size/2, height/2)
glVertex2f(width/2, height/2 - cross_size/2)
glVertex2f(width/2, height/2 + cross_size/2)
glEnd()
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
Ensure that 2 dimensional texturing is disabled (glDisable(GL_TEXTURE_2D))

Pyglet: How to change resolution when you go fullscreen?

I'm using Pyglet and I have a little that includes an object moving over a background. Both of them are represented by images (png and jpg).
I've created a non-fullscreen window with size 800x600 and it works fine, but when I toggle to fullscreen... background and object have the same size as before and the rest of the screen is filled with black (empty color).
What I want to do is to "scale" the images or change the resolution when I toggle fullscreen mode.
I've read the documentation, but I can't find the answer to this.
I know that with Pygame, this problem solves itself automatically (if you change the window size, everything rescales automatically)... but how do you do this with pyglet?
This is my relevant code:
import pyglet
WIDTH = 800
HEIGHT = 600
working_dir = '/where/i/have/my/images/'
window = pyglet.window.Window(WIDTH, HEIGHT)
background = pyglet.image.load(working_dir + 'background.jpg')
flying_thing = pyglet.image.load(working_dir + 'flying_thing.png')
#window.event
def on_draw():
window.clear()
background.blit(0, 0)
flying_thing.blit(WIDTH // 2, HEIGHT // 2)
#window.event
def on_key_press(symbol, modifiers):
if symbol == pyglet.window.key.SPACE:
window.set_fullscreen(not window.fullscreen)
pyglet.app.run()
You can try this code changing working_dir, background.jpg and flying_thing.png to a working directory of yours and two images in it.
I didn't tried, but from pyglet docs, blit supports width and height. Its signature is
blit(self, x, y, z=0, width=None, height=None)
Have you tried using
background.blit(width=window.width, height=windows.height)
instead? (I'm not sure the window.width changes on full_screen, let's see...).
This answer can also be relevant to your question: https://stackoverflow.com/a/11183462/931303.

Why are PNG images rendered from a QGraphicsScene being incorrectly offset?

I have a program that draws the contents of a QGraphicsScene to a PNG image. I have found that in some circumstances the PNG image is not being created correctly. The image generated appears to be 'offset' by some amount, in that the top-left corner of the QGraphicsScene isn't at (0,0) in the PNG image.
The code below demonstrates the problem. The createImage function creates a QGraphicsScene of a given width and height, draws at coordinates (0, 0) a solid red rectangle of the same width and height as the scene, renders this scene to an image and writes the image out to a file. I believe that this function should always write out an image that is solid red, regardless of the width and height it is given. However, here is one example output image which demonstrates that this is not the case.
The testImage function (which relies on PIL) reads in the image and inspects the rightmost column and bottom row. If the image has been incorrectly 'offset', it will find a white pixel somewhere within the bottom row or rightmost column. If the image has been generated correctly, the bottom row and rightmost column will contain only red pixels. (PIL isn't part of the problem here; I'm just using it to automate the testing of the output images.)
#!/usr/bin/env python
import sys
import Image # Requires PIL
from PyQt4.QtCore import Qt, QRect, QRectF
from PyQt4.QtGui import QPen, QBrush, QGraphicsScene, QGraphicsView, QGraphicsRectItem, QPixmap, QPainter, QApplication
whitebrush = QBrush(Qt.white)
redbrush = QBrush(Qt.red)
RED = (255, 0, 0)
def createImage(width, height):
scene = QGraphicsScene(0, 0, width, height)
scene.setBackgroundBrush(whitebrush)
rectangle = QGraphicsRectItem(0, 0, width, height)
rectangle.setPen(QPen(Qt.NoPen))
rectangle.setBrush(redbrush)
rectangle.show()
scene.addItem(rectangle)
view = QGraphicsView()
view.setScene(scene)
outputimg = QPixmap(width, height)
painter = QPainter(outputimg)
targetrect = QRectF(0, 0, width, height)
sourcerect = QRect(0, 0, width, height)
view.render(painter, targetrect, sourcerect)
outputimg.save("output.png", "PNG")
painter.end()
def testImage(width, height):
image = Image.open("output.png")
ok = True
for x in range(width):
if image.getpixel((x, height - 1)) == RED:
if x > 0:
print "First red pixel on bottom row is at x = %d" % x
ok = False
break
else:
print "No red pixels found on bottom row"
ok = False
for y in range(height):
if image.getpixel((width - 1, y)) == RED:
if y > 0:
print "First red pixel in rightmost column is at y = %d" % y
ok = False
break
else:
print "No red pixels found in rightmost column"
ok = False
if ok:
print "Image OK"
def doTest(width, height):
createImage(width, height)
testImage(width, height)
if __name__ == "__main__":
app = QApplication(sys.argv)
doTest(int(sys.argv[1]), int(sys.argv[2]))
Here are a few example runs:
$ ./qgraphicssceneimagetest.py 200 200
No red pixels found on bottom row
No red pixels found in rightmost column
In this case the effect of the 'offsetting' is so drastic that the entire output image is white.
$ ./qgraphicssceneimagetest.py 400 400
First red pixel on bottom row is at x = 117
First red pixel in rightmost column is at y = 37
This is the case that generated the sample image I linked to above.
$ ./qgraphicssceneimagetest.py 500 500
First red pixel on bottom row is at x = 55
At this point, the image is now correctly offset vertically, but the red rectangle is still being drawn 55 pixels too far to the right.
$ ./qgraphicssceneimagetest.py 600 600
First red pixel on bottom row is at x = 5
Nearly correct now...
$ ./qgraphicssceneimagetest.py 700 700
Image OK
So it seems that the code can generate an output image correctly if the image is large enough.
As it happens, I have a workaround for this problem. I can get the createImage function to create the images correctly if I replace the line
sourcerect = QRect(0, 0, width, height)
with
xoff = 0
yoff = 0
if width <= 634 and height <= 474:
xoff = (634 - width) // 2
yoff = (474 - height) // 2
elif width < 610:
xoff = (610 - width) // 2
elif height < 450:
yoff = (450 - height) // 2
sourcerect = QRect(xoff, yoff, width, height)
I do not know what the significance of the 'magic' numbers in this code is. However, I have run numerous tests with this workaround in place and have verified that they all generated a correct solid red image.
Having this workaround is all right, but I'd like to understand why these images aren't being created correctly. Is there something I've missed or forgotten to do?
What is happening, is that the view is automatically calculating a rect for the scene and then centring itself on the graphics items that have been added. Depending on the dimensions of the items, this may sometimes leave blank areas around the edges.
The solution is to explicitly set a rect for the scene:
view = QGraphicsView()
view.setScene(scene)
view.setSceneRect(QRectF(view.viewport().rect()))

PIL Image.fromstring from PyOpengl buffer has the wrong size

I use PyOpenGL to draw a 2D Image. Then I want to use the Python Imaging Library (PIL) to store this image to disk. I use GLUT to display the image which works perfectly. But when I use PIL to store the image it extracts the wrong clipping. It has the wrong size.
Here is a minimal example which reproduces the effect and I also attach the output to make it more clear without running some code.
from OpenGL.GL import *
from OpenGL.GLUT import *
from PIL import Image
width, height = 640, 480
def DrawStuff():
poly1 = [(0,0), (640,0), (0,480)]
color = (0.5, 0.4, 0.3, 0.8)
glClear(GL_COLOR_BUFFER_BIT)
glPushMatrix()
glLineWidth(5.0)
glColor4f(*color)
glBegin(GL_POLYGON)
glVertex2f(poly1[0][0], poly1[0][1])
glVertex2f(poly1[1][0], poly1[1][1])
glVertex2f(poly1[2][0], poly1[2][1])
glVertex2f(poly1[0][0], poly1[0][1])
glEnd() # GL_POLYGON
glPopMatrix()
glPixelStorei(GL_PACK_ALIGNMENT, 1)
data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
image = Image.fromstring("RGBA", (width, height), data)
image.show()
image.save('out.png', 'PNG')
glutSwapBuffers()
# glut initialization
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)
glutCreateWindow("Draw Polygons")
glutInitWindowSize(width, height)
# set the function to draw
glutDisplayFunc(DrawStuff)
# enable the alpha blending
glEnable(GL_BLEND)
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
# prepare for 2D drawing
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, height, 0, 0, 1)
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)
# start the mainloop
glutMainLoop ()
this is how it looks int the GLUT window and how it is supposed to look like
and this is how the saved image looks like
I managed to solve my own Problem.
First I tried the following solution which might also help people with related problems:
solution1
But then, through extensive trial and error, I found that the solution is much simpler.
I simply had to swap two lines from:
glutCreateWindow("Draw Polygons")
glutInitWindowSize(width, height)
to
glutInitWindowSize(width, height)
glutCreateWindow("Draw Polygons")
Apparently the size has to be set before the window
You should consider that in OpenGL the coordinate system starts at different place than in PIL. Look at this.

coordinates changed when migrating from pygame+rabbyt to pyglet+rabbyt

I'm working on a 2D game and decided to switch from SDL to OpenGL. I took rabbyt as an opengl wrapper for rendering my sprites and using pymunk (chipmunk) for my physics. I used pygame for creating the window and rabbyt for drawing the sprites on the screen.
I discovered that with pygame+rabbyt the (0,0) coordinate is in the middle of the screen. I liked that fact, because the coordinate representation in the physics engine were the same as in my graphics engine (I don't have to recalculate the coordinates when rendering the sprites).
Then I switched to pyglet because I wanted to draw lines with OpenGL - and discovered that suddenly the (0,0) coordinate was at the bottom left of the screen.
I suspected that that has something to do with the glViewport function, but only rabbyt executes that function, pyglet touches it only when the window is resized.
How can I set the (0,0) coordinate at the middle of the Screen?
I'm not very familiar with OpenGL and couldn't find anything after several hours googling and trial&error... I hope someone can help me :)
Edit: Some additional information :)
This is my pyglet screen initialization code:
self.window = Window(width=800, height=600)
rabbyt.set_viewport((800,600))
rabbyt.set_default_attribs()
This is my pygame screen initialization code:
display = pygame.display.set_mode((800,600), \
pygame.OPENGL | pygame.DOUBLEBUF)
rabbyt.set_viewport((800, 600))
rabbyt.set_default_attribs()
Edit 2: I looked at the sources of pyglet and pygame and didn't discover anything in the screen initialization code that has something to do with the OpenGL viewport... Here is the source of the two rabbyt functions:
def set_viewport(viewport, projection=None):
"""
``set_viewport(viewport, [projection])``
Sets how coordinates map to the screen.
``viewport`` gives the screen coordinates that will be drawn to. It
should be in either the form ``(width, height)`` or
``(left, top, right, bottom)``
``projection`` gives the sprite coordinates that will be mapped to the
screen coordinates given by ``viewport``. It too should be in one of the
two forms accepted by ``viewport``. If ``projection`` is not given, it
will default to the width and height of ``viewport``. If only the width
and height are given, ``(0, 0)`` will be the center point.
"""
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
if len(viewport) == 4:
l, t, r, b = viewport
else:
l, t = 0, 0
r, b = viewport
for i in (l,t,r,b):
if i < 0:
raise ValueError("Viewport values cannot be negative")
glViewport(l, t, r-l, b-t)
if projection is not None:
if len(projection) == 4:
l, t, r, b = projection
else:
w,h = projection
l, r, t, b = -w/2, w/2, -h/2, h/2
else:
w,h = r-l, b-t
l, r, b, t = -w/2, w/2, -h/2, h/2
glOrtho(l, r, b, t, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set_default_attribs():
"""
``set_default_attribs()``
Sets a few of the OpenGL attributes that sprites expect.
Unless you know what you are doing, you should call this at least once
before rendering any sprites. (It is called automatically in
``rabbyt.init_display()``)
"""
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
glEnable(GL_BLEND)
#glEnable(GL_POLYGON_SMOOTH)
Thanks,
Steffen
As l33tnerd suggested the origin can be placed at the center with glTranslatef...
I added the following below my screen initialization code:
pyglet.gl.glTranslatef(width/2, height/2, 0)
Thanks!

Categories