Pyglet render into texture - python

I'm writing a drawing program using pyglet, and I want to be able to have the image being created as separate from the window's buffer (for instance, the image could be larger than the window, or may want to draw to this image at a different rate than the main window is being re-drawn). I want to be able to draw into this off-screen image, then display it in the window, but pyglet doesn't allow drawing to anything else than a window. Is there any simple way I can do this?
I've tried creating a second hidden pyglet window, but this gets rendered at the same rate as the main window which I definitely don't want.
The closest I found was Pyglet draw text into texture, but the code there isn't complete, and also no longer works as the opengl version used by pyglet has moved on.

The following code works for me, perhaps someone else can improve on my answer:
import pyglet
from pyglet.gl import *
from ctypes import byref
W, H = 800, 600
image = pyglet.image.create(W, H)
texture = image.get_texture()
window = pyglet.window.Window(width=W, height=H)
fbo_id = gl.GLuint(0)
glGenFramebuffers(1, byref(fbo_id))
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
glBindTexture(GL_TEXTURE_2D, texture.id)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0)
rect1 = pyglet.shapes.Rectangle(0, 0, W, H, (255, 0, 0) )
rect2 = pyglet.shapes.Rectangle(W//4, H//4, W//2, H//2, (0, 0, 255) )
label = pyglet.text.Label("Hello World", font_name="Times New Roman", font_size=36,
x=W//2, y=H//2, anchor_x="center", anchor_y="center")
rect1.draw()
rect2.draw()
label.draw()
#window.event
def on_mouse_drag(x, y, dx, dy, xxx, modifiers):
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
line = pyglet.shapes.Line(x-dx, y-dy, x, y, 3, (0, 255, 255))
line.draw()
#window.event
def on_draw():
glBindFramebuffer(GL_FRAMEBUFFER, 0)
window.clear()
texture.blit(0, 0)
pyglet.app.run()

Related

Python Pyglet, new sprites in batch

Python, pyglet
I want to add sprites to my window after it was drawn.
i try to use batch, becouse i want to to have lot of sprites.
my simple testing code is:
import pyglet
import random
from pyglet.window import key, mouse
from pyglet import image
from PIL import Image, ImageDraw, ImageFont
#function return pyglet image 10x10px triangle, random color. Instead of loading a file, work fine
def pyimg():
background = Image.new('RGBA', (10,10), (255,255,255,0) )
img = Image.new('RGBA', (10,10), (0,0,0,0) )
draw = ImageDraw.Draw(img)
draw.polygon([(0,0), (0,10), (10,0)], fill=(random.randint(0,255),random.randint(0,255),random.randint(0,255)))
im= Image.alpha_composite(background, img)
return pyglet.image.ImageData(im.width, im.height, 'RGBA', im.tobytes(), pitch=-im.width * 4)
screenWidth=1280
window = pyglet.window.Window(screenWidth, 720, resizable=True)
batch=pyglet.graphics.Batch()
sprites=[]
sprites.append(pyglet.sprite.Sprite(pyimg(), x=50, y=50, batch=batch))
sprites.append(pyglet.sprite.Sprite(pyimg(), x=150, y=50, batch=batch))
i=30
#window.event
def on_draw():
window.clear()
batch.draw()
#window.event
def on_key_press(symbol, modifiers):
global i
if symbol == key.RETURN:
print("Return key pressed")
sprites.append(pyglet.sprite.Sprite(pyimg(), x=150+i, y=50))
i+=15
pyglet.app.run()
App displays two sprites, and should add one more after enter key pressed. How to update display?
This line: sprites.append(pyglet.sprite.Sprite(pyimg(), x=150+i, y=50)) you aren't specifying the batch to add sprites into.
It needs to be sprites.append(pyglet.sprite.Sprite(pyimg(), x=150+i, y=50, batch=batch))

Pyglet ignores alpha channel

I'm trying to make a level editor for a game I'm writing in Python using Pyglet. I want to be able to modify the darkness of any block I place. As such, I have added a darkness attribute to the Block class. Here is that class:
class Block:
def __init__(self, name, image, wall):
self.name = name
self.image = image
self.wall = wall
self.darkness = 0
def set_darkness(self, darkness):
self.darkness = darkness
def draw(self, x, y):
self.image.blit(x, y)
pyglet.graphics.draw(4, GL_QUADS, ('v2i', (x, y, x + block_width, y, x + block_width, y + block_height, x, y + block_width)), ('c4B', (0, 0, 0, self.darkness) * 4))
I am trying to darken an image by drawing a black rectangle on top of it, and then adjusting the transparency of that rectangle. However, no matter what I set self.darkness to in __init__, the rectangle continues to be completely opaque.
I tried enabling color blending with pyglet.gl by adding this code to the top of my program:
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
This changed nothing. I also tried using Pyglet's SolidColorImagePattern class to create a black rectangle with the alpha channel set to self.darkness, but that also did nothing. How do I make this rectangle transparent?
For any OpenGL instruction, a valid and current OpenGL Context is of need. Of course pyglet provides an OpenGL context, but the context is created and made current when the pyglet (OpenGL) window is created (see pyglet.window).
Create the window, before setting up Blending to solve the issue:
from pyglet.gl import *
window = pyglet.window.Window(400, 400, "OpenGL window")
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

OpenGL render view without a visible window in python

I need to render some scene. I managed to do it in python using pyopengl and pygame. The problem is that it creates a window for a short period of time.
I want to render the same image and save it, without creating a visible window (Or possibly without creating a window at all, and even without pygame).
import cv2
import numpy as np
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
def main():
DISPLAY_WIDTH = 900
DISPLAY_HEIGHT = 900
pygame.init()
pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT), DOUBLEBUF | OPENGL)
gluPerspective(90, (DISPLAY_WIDTH / DISPLAY_HEIGHT), 0.01, 12)
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glRotatef(-90, 1, 0, 0) # Straight rotation
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glRotatef(285, 0, 0, 1) # Rotate yaw
glTranslatef(-5, -3, -2) # Move to position
# Draw rectangle
glBegin(GL_QUADS)
glColor3f(1, 0, 0)
glVertex3f(2, 2, 0)
glVertex3f(2, 2, 2)
glVertex3f(2, 6, 2)
glVertex3f(2, 6, 0)
glEnd()
image_buffer = glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, OpenGL.GL.GL_RGB, OpenGL.GL.GL_UNSIGNED_BYTE)
image = np.frombuffer(image_buffer, dtype=np.uint8).reshape(DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)
cv2.imwrite(r"C:\temp\image.png", image)
pygame.quit()
if __name__ == "__main__":
main()
I want to render the same image and save it, without creating a visible window
It is not possible to create an OpenGL Context with an version above 1.0 without any window.
See the answer to the question Creating OpenGL context without window.
But it is possible to use a completely hidden window for "offscreen" rendering.
Sadly it is not possible to create a initially hidden window with Pygame.
It is only possible to hide a window after it was created by pygame.display.iconify().
See also Hiding pygame display.
But it is possible to create a initially hidden window with the GLFW library by setting the window hint VISIBLE to False.
The glfw library can be found at glfw 1.7.0.
The code may look like this:
import cv2
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
import glfw
def main():
DISPLAY_WIDTH = 900
DISPLAY_HEIGHT = 900
# Initialize the library
if not glfw.init():
return
# Set window hint NOT visible
glfw.window_hint(glfw.VISIBLE, False)
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(DISPLAY_WIDTH, DISPLAY_HEIGHT, "hidden window", None, None)
if not window:
glfw.terminate()
return
# Make the window's context current
glfw.make_context_current(window)
gluPerspective(90, (DISPLAY_WIDTH / DISPLAY_HEIGHT), 0.01, 12)
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glRotatef(-90, 1, 0, 0) # Straight rotation
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glRotatef(285, 0, 0, 1) # Rotate yaw
glTranslatef(-5, -3, -2) # Move to position
# Draw rectangle
glBegin(GL_QUADS)
glColor3f(1, 0, 0)
glVertex3f(2, 2, 0)
glVertex3f(2, 2, 2)
glVertex3f(2, 6, 2)
glVertex3f(2, 6, 0)
glEnd()
image_buffer = glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, OpenGL.GL.GL_RGB, OpenGL.GL.GL_UNSIGNED_BYTE)
image = np.frombuffer(image_buffer, dtype=np.uint8).reshape(DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)
cv2.imwrite(r"C:\temp\image.png", image)
glfw.destroy_window(window)
glfw.terminate()
if __name__ == "__main__":
main()

Pyglet copy and paste a text in an IncrementalTextLayout() object

Is there a way to make a simple "copy and paste" in Pyglet?
I need to copy a text (ctrl + c) and paste (ctrl + v) it into a IncrementalTextLayout() object in Pyglet, is this possible?
I'm using Python 3.4, Pyglet 1.2.4 and I run on Windows.
Code example:
import pyglet
if __name__ == "__main__":
window = pyglet.window.Window(617, 200)
batch = pyglet.graphics.Batch()
document = pyglet.text.document.FormattedDocument("Colar texto aqui!")
document.set_style(0, len(document.text), dict(font_name='Arial', font_size=25, color=(255, 255, 255, 255)))
layout = pyglet.text.layout.IncrementalTextLayout(document, 300, 50, batch=batch)
caret = pyglet.text.caret.Caret(layout, color=(255, 255, 255))
window.push_handlers(caret)
#window.event
def on_draw():
"""Desenha a tela."""
window.clear()
batch.draw()
window.push_handlers(caret)
pyglet.app.run()
Another developer resolved this with the Pyperclip and put your functions in the on_key_press method of the Window in Pyglet. Follow the code bellow:
def on_key_press(self, symbol, modifiers):
if modifiers is 18 and pyglet.window.key.MOD_CTRL and int(symbol) is pyglet.window.key.V:
if self.input_text:
self.on_text(pyperclip.paste())
elif modifiers is 18 and pyglet.window.key.MOD_CTRL and int(symbol) is pyglet.window.key.C:
pyperclip.copy(self.input_text.document.text)

wxPython - GC.DrawText removes background bitmap

I'm trying to draw a text on existing bitmap, but when I use the Graphics Context's DrawText method, the background is removed. But this happens only when I create background image from empty bitmap (using DrawText on Bitmap from loaded image works well).
I think that the issue happens because I'm using MemoryDC to create an empty bitmap, but I'm quite new to wxPython, so I have no idea how to fix it.
Here's what I've done so far:
import wx
def GetEmptyBitmap(w, h, color=(0,0,0)):
"""
Create monochromatic bitmap with desired background color.
Default is black
"""
b = wx.EmptyBitmap(w, h)
dc = wx.MemoryDC(b)
dc.SetBrush(wx.Brush(color))
dc.DrawRectangle(0, 0, w, h)
return b
def drawTextOverBitmap(bitmap, text='', fontcolor=(255, 255, 255)):
"""
Places text on the center of bitmap and returns modified bitmap.
Fontcolor can be set as well (white default)
"""
dc = wx.MemoryDC(bitmap)
gc = wx.GraphicsContext.Create(dc)
font = wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
gc.SetFont(font, fontcolor)
w,h = dc.GetSize()
tw, th = dc.GetTextExtent(text)
gc.DrawText(text, (w - tw) / 2, (h - th) / 2)
return bitmap
app = wx.App()
bmp_from_img = bmp = wx.Image(location).Rescale(200, 100).ConvertToBitmap()
bmp_from_img = drawTextOverBitmap(bmp_from_img, "From Image", (255,255,255))
bmp_from_empty = GetEmptyBitmap(200, 100, (255,0,0))
bmp_from_empty = drawTextOverBitmap(bmp_from_empty, "From Empty", (255,255,255))
frame = wx.Frame(None)
st1 = wx.StaticBitmap(frame, -1, bmp_from_img, (0,0), (200,100))
st2 = wx.StaticBitmap(frame, -1, bmp_from_empty, (0, 100), (200, 100))
frame.Show()
app.MainLoop()
As I said, the StaticBitmap which uses the loaded image is displayed correctly, but the one created with EmptyBitmap has no background.
Do you have any ideas how to make it work?
Thank you
This seems like a bug to me. Use the following to make it work:
def GetEmptyBitmap(w, h, color=(0,0,0)):
# ...
# instead of
# b = wx.EmptyBitmap(w, h)
# use the following:
img = wx.EmptyImage(w, h)
b = img.ConvertFromBitmap()
# ...
I think not the wx.MemoryDC is to blame, but the platform-specific bitmap creation routines, where there is more going on under the hood. By starting off with a wx.Image the output seems to be more predictable/useful.

Categories